// ==========================================================================
// SproutCore -- JavaScript Application Framework
// copyright 2006-2007, Sprout Systems, Inc. and contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a 
// copy of this software and associated documentation files (the "Software"), 
// to deal in the Software without restriction, including without limitation 
// the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the 
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in 
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
// DEALINGS IN THE SOFTWARE.
//
// For more information about SproutCore, visit http://wiki.sproutit.com/core
//
//
// ==========================================================================


SproutCore = {} ; SC = SproutCore ;

var YES = true ; var NO = false ;

function require(file) { return null ; }

if (!window.console) { 
 window.console = { 
   _output: [],
   log: function(str) { this._output.push(str) ; },
   tail: function(lines) { 
     if (!lines) lines = 1 ;
     var loc = this._output.length - lines ;
     if (loc < 0) loc = 0 ;
     var ret = [] ;
     while(loc < this._output.length) {
       ret.push(this._output[loc]) ; loc++ ;
     }
     return ret.join("\n");
   }  
 } ;
}
window.logCount = 0 ;


Object.extend(SC,{

  _downloadFrames: 0, // count of download frames inserted into document
  
	download: function(path) {
    var tempDLIFrame=document.createElement('iframe');
    var frameId = 'DownloadFrame_' + this._downloadFrames;
    tempDLIFrame.setAttribute('id',frameId);
    tempDLIFrame.style.border='10px';
    tempDLIFrame.style.width='0px';
    tempDLIFrame.style.height='0px';
    tempDLIFrame.style.position='absolute';
    tempDLIFrame.style.top='-10000px';
    tempDLIFrame.style.left='-10000px';    
    if (!(SC.isSafari())) {
      tempDLIFrame.setAttribute('src',path);    
    }
    document.getElementsByTagName('body')[0].appendChild(tempDLIFrame);
    if (SC.isSafari()) {
      tempDLIFrame.setAttribute('src',path);    
    }
    this._downloadFrames = this._downloadFrames + 1;
    if (!(SC.isSafari())) {
      var r = function() { 
        document.body.removeChild(document.getElementById(frameId)); 
      } ;
      var t = setTimeout(r,2000);
    }
	},
		
  callOnLoad: function(func) { 
    if (SC._onloadQueueFlushed) func.call(document);
    var queue = SC._onloadQueue || [] ;
    queue.push(func) ; SC._onloadQueue = queue ;
  },

  didLoad: function() { 
    
    var b = $tag('body');
    Element.addClassName(b, String.currentLanguage().toLowerCase()) ;

    var queue ;
    if (window.callOnLoad) {
      if (window.callOnLoad instanceof Array) {
        queue = window.callOnLoad ;
      } else if (window.callOnLoad instanceof Function) {
        queue = [window.callOnLoad] ;
      }
    } else queue = [] ;
    queue = queue.concat(SC._onloadQueue) ;
    var func = null ;
    while(func = queue.shift()) func.call(document) ;
    SC._onloadQueueFlushed = true ;
        
    if (window.main && (main instanceof Function)) main() ; // start app.
    
    if (typeof Routes != 'undefined') {
      Routes.doRoutes() ; // old style.
    } else if (typeof SC.Routes != 'undefined') {
      SC.Routes.ping() ; // handle routes, if modules is installed.
    }

  },
  
  isIE: function() { 
    return Prototype.Browser.IE ;
  },
  
  isSafari: function() {
    return Prototype.Browser.WebKit ;
  },
  
  isSafari3: function() {
    if (SC.isSafari()) {
      var vers = parseInt(navigator.appVersion.replace(/^.*?AppleWebKit\/(\d+).*?$/,'$1'),0) ;
      return vers > 420 ;
    } else return false ;
  },
  
  isIE7: function() {
    return !!(navigator.appVersion.match(/\bMSIE.*7\.\b/)) ;
  },

  isIE6: function() {
	return SC.isIE() && !(SC.isIE7());
  },

  isWindows: function() {
    return !!(navigator.appVersion.match(/(Windows)/)) ;
  },

  isMacOSX: function() {
	if(SC.isFireFox()) return !!(navigator.appVersion.match(/(Macintosh)/));
    return !!(navigator.appVersion.match(/(Mac OS X)/)) ;
  },
  
  isFireFox: function() {
    return Prototype.Browser.Gecko ;
  },
  
  isFireFox2: function() {
    return !!(navigator.userAgent.indexOf('Firefox/2')>=0) ;
  },
  
  normalizeURL: function(url) {
    if (url.slice(0,1) == '/') {
      url = window.location.protocol + '//' + window.location.host + url ;
    } else if ((url.slice(0,5) == 'http:') || (url.slice(0,6) == 'https:')) {
    } else {
      url = window.location + '/' + url ;
    }
    return url ;
  },
  
  typeOf: function(item) {
    if (item === undefined) return T_UNDEFINED ;
    if (item === null) return T_NULL ; 
    var ret = typeof(item) ;
    if (ret == "object") {
      if (item instanceof Array) {
        ret = T_ARRAY ;
      } else if (item instanceof Function) {
        ret = (item.isClass) ? T_CLASS : T_FUNCTION ;
      } else if (item instanceof SC.Error) {
        ret = T_ERROR ;        
      } else if (item.isObject == true) {
        ret = T_OBJECT ;
      } else ret = T_HASH ;
    } else if (ret == T_FUNCTION) ret = (item.isClass) ? T_CLASS : T_FUNCTION;
    return ret ;
  },
  
  isEqual: function(a,b) {
    if (a === null) {
      return b === null ;
    } else if (a === undefined) {
      return b === undefined ;
    } else if (typeof(a) == typeof(b)) {
      return a == b ;
    }
  }
  
}) ;

T_ERROR = 'error' ;
T_OBJECT = 'object' ;
T_NULL = 'null';
T_CLASS = 'class' ;
T_HASH = 'hash' ;
T_FUNCTION = 'function' ;
T_UNDEFINED = 'undefined' ;
T_NUMBER = 'number' ;
T_BOOL = 'boolean' ;
T_ARRAY = 'array' ;

$type = function(item) { return SC.typeOf(item); }; 

Object.extend(Object,{

  serialize: function(obj) {
    var ret = [] ;
    for(var key in obj) {
      var value = obj[key] ;
      if (typeof value == 'number') { value = '' + value ; }
      if (!(typeof value == 'string')) { value = value.join(','); }
      ret.push(encodeURIComponent(key) + "=" + encodeURIComponent(value)) ;
    }
    return ret.join('&') ;
  }
  
}) ;

Object.extend(String.prototype,{

  capitalize: function() {
    var words = this.split(' ') ;
    words = words.map(function(word) {
      if (word.length == 0) return word ;
      return word.charAt(0).toUpperCase() + word.substring(1) ;
    }) ;
    return words.join(' ') ;
  },

  format: function() {
    var args = $A(arguments) ;
    var parts = this.split('%@') ;
    var ret = [] ;
    while(parts && parts.length > 0) {
      ret.push(parts.shift()) ;
      if (args && args.length>0) ret.push(args.shift()) ;
    }
    return ret.join('') ;
  },

  loc: function() {
    var kit = String[String.currentLanguage()];
    var str = kit[this] ;
    if (!str) str = String.English[this] || this ;
    return str.format.apply(str,arguments) ;
  },
  
  decamelize: function() { 
    return this.replace(/([a-z])([A-Z])/g,'$1-$2');
  },

  toHref: function() {
    if (this.match(/.+@.+\...+/)) {
      return 'mailto:' + this;
    } else if (this.indexOf('http://') != 0 && this.indexOf('https://') !=0 && this.match(/[^.]+\.[^.]+/)) {
      return 'http://' + this;
    } else {
      return this;
    }
  },
  
  trim: function ()
  {
    return this.replace(/^\s+|\s+$/g,"");
  },
  
  strip: function()
  {
    return this.trim();
  }
  
}) ;

Object.extend(String,{
  
  currentLanguage: function () {
    var browserLanguage = (navigator.language || navigator.browserLanguage).split('-', 1);
    switch(browserLanguage[0])
    {
      case 'fr':
        return 'French';
      case 'de':
        return 'German';
      case 'ja':
        return 'Japanese';
      default:
        return 'English'; 
    }
  },
  English: {},
  French: {},
  German: {},
  Japanese: {}
}) ;

Array.prototype.isEqual = function(ary) {
  if (!ary) return false ;
  if (ary.length != this.length) return false ;
  for(var loc=0;loc<ary.length;loc++) {
    if (ary[loc] != this[loc]) return false ;
  }
  return true ;
} ;

Array.asArray = function (array)
{
  if(array && !(array instanceof Array))
  {
    return [array]; 
  }
  if(array)
  {
    return array;
  }
  return [];
};


Element.setClassName = function(element,className,flag) {
  if (flag) { 
    element.addClassName(className); 
  } else {
    element.removeClassName(className) ;
  }
} ;

Object.extend(Event,{
  getCharCode: function(e) {
    return (e.keyCode) ? e.keyCode : ((e.which)?e.which:0) ; 
  },
  
  getCharString: function(e) {
    return String.fromCharCode(Event.getCharCode(e)) ;
  },
  
  ALT_KEY: '_ALT',
  CTRL_KEY: '_CTRL',
  SHIFT_KEY: '_SHIFT'
  
});



require('Core') ;


SC.Object = function(noinit) { 
	if (noinit == SC.Object._noinit_) return ;
	var ret = SC.Object._init.apply(this,$A(arguments)) ;
  return ret ;
};

Object.extend(SC.Object, {

	_noinit_: '__noinit__',
	
  mixin: function() {
    var ext = $A(arguments) ;
    for(var loc=0;loc<ext.length;loc++) {
      Object.extend(this,ext[loc]);
    }
    return this ;
  },
  
  extend: function() {    
    var r = SC.idt.active ; var idtStart ;
    if (r) {
      SC.idt.e_count++ ;
      idtStart = new Date().getTime() ;
    }
    
    var ret = function(noinit) { 
      if (noinit && (typeof(noinit) == 'string') && (noinit == SC.Object._noinit_)) return ;
      var ret = SC.Object._init.apply(this,$A(arguments)); 
      return ret ;
    };
    for(var prop in this) { ret[prop] = this[prop]; }

    var base = new this(SC.Object._noinit_) ;

    var extensions = $A(arguments) ;
    for(var loc=0;loc<extensions.length;loc++) {
      base = SC.Object._extend(base, extensions[loc]);
    }
    ret.prototype = base ;
    
    ret._guid = SC._nextGUID++ ; // each time we extend, get a new guid.
    
    if (r) SC.idt.e_t += ((new Date().getTime()) - idtStart) ;
    
    return ret ;
  },
  
  create: function() {
    var ret = new this($A(arguments),this) ;
    return ret ;
  },
    
  createArray: function(array) {
    var obj = this ;
    return array.map(function(props) { return obj.create(props); }) ;
  },

  outlet: function() {
    var obj = this ;
    return function() {
      var ret = obj.create() ; ret.owner = this ; return ret ;
    } ;
  },
  
  isClass: true,
  
  tupleForPropertyPath: function(path,root) {
    if (path.constructor == Array) return path ;
    
    var parts = path.split('*') ; var key = null ;
    if (parts && parts.length > 1) {
      key = parts.pop(); path = parts.join('*') ;
    }
    
    parts = path.split('.') ;
    if (!key) key = parts.pop() ;
    
    var obj = this.objectForPropertyPath(parts,root) ;
    return (obj && key) ? [obj,key] : null ;
  },
  
  objectForPropertyPath: function(path,root) {
    var parts = (typeof(path) == "string") ? path.split('.') : path ;
    if (!root) root = window ;
    var key = parts.shift() ;
    while(key && root) { 
      root = (root.get) ? root.get(key) : root[key]; 
      key = parts.shift(); 
    }
    return (parts.length > 0) ? undefined : root ;  
  },
  
  
  _init: function(extensions,type) {
    
    var ret = this ;
    for(var loc=0;loc<extensions.length;loc++) {
      ret = SC.Object._extend(ret,extensions[loc]) ;
    }
    ret._guid = SC._nextGUID++ ;
    ret._type = type ;
    ret.init() ;
    
    return ret ;
  },

  _extend: function(base,ext) { return this._extendAllProps(false, base, ext); },
  
  _extendAllProps: function(allProperties, base,ext) {
    var cprops = base._cprops ; var f = Prototype.emptyFunction ;
    
    var concats = {} ;
    if (cprops) for(var cloc=0;cloc<cprops.length;cloc++) {
      var p = cprops[cloc]; var p1 = base[p]; var p2 = ext[p] ;
      p1 = (p1 && p2) ? p1.concat(p2) : (p1 || p2) ;
      concats[p] = p1 ;
    }
    
    var bindings = (ext._bindings) ? null : (base._bindings || []).slice() ;
    var observers = (ext._observers) ? null : (base._observers || []).slice();
    var properties = (ext._properties) ? null : (base._properties || []).slice() ;
    
    for(var key in ext) {
      if (!allProperties && !ext.hasOwnProperty(key)) continue ; 

      var value = (concats.hasOwnProperty(key) ? concats[key] : null) || ext[key] ;
      if (value && (value instanceof Function) && (!value.base)) {
        if (value != base[key]) value.base = base[key] || f;          
      }
      
      var keyLen = key.length ;
      if (observers && (key.slice(keyLen-8,keyLen) == "Observer")) {
        observers.push(key) ;
      } else if (bindings && (key.slice(keyLen-7,keyLen) == "Binding")) {
        bindings.push(key) ;
      } else if (properties && value && (value instanceof Function) && value.dependentKeys) {
        properties.push(key) ;
      }

      base[key] = value ;
    }

    if (bindings) base._bindings = bindings;
    if (observers) base._observers = observers ;
    if (properties) base._properties = properties ;

    
    return base ;
  },
  
  toString: function() { return 'Object('+this._guid+')'; }
}) ;

SC.idt = { count: 0, t: 0.0, keys: 0, observers: 0, bindings: 0, pv: 0, observers_t: 0, bindings_t: 0, pv_t: 0, conf_t: 0, b1_t: 0, b2_t: 0, b3_t: 0, e_count: 0, e_t: 0, v_count: 0, v_t: 0, vc_t: 0 ,active: false } ;

SC.report = function() {
  var c = SC.idt.count ;
  var e = SC.idt.e_count ;
  var v = SC.idt.v_count ;
  var ret = [] ;
  ret.push('CREATED: ' + c + ' (avg time: '+(Math.floor(SC.idt.t * 100 / c) / 100)+' msec)') ;
  ret.push('EXTENDED: ' + e + ' (avg time: '+(Math.floor(SC.idt.e_t * 100 / e) / 100)+' msec)') ;
  ret.push('AVG KEYS: ' + (Math.floor(SC.idt.keys * 100 / c) / 100)) ;
  ret.push('AVG OBSERVERS: ' + (Math.floor(SC.idt.observers * 100 / c) / 100)  + ' ('+ (Math.floor(SC.idt.observers_t * 100 / c) / 100) + ' msec)') ;
  ret.push('AVG BINDINGS: ' + (Math.floor(SC.idt.bindings * 100 / c) / 100)  + ' ('+ (Math.floor(SC.idt.bindings_t * 100 / c) / 100) + ' msec)') ;
  ret.push('AVG PV: ' + (Math.floor(SC.idt.pv * 100 / c) / 100)  + ' ('+ (Math.floor(SC.idt.pv_t * 100 / c) / 100) + ' msec)') ;
  ret.push('AVG CONFIGURE OUTLETS: ' + (Math.floor(SC.idt.conf_t * 100 / c) / 100)  + ' msec') ;
  ret.push('AVG B1: ' + (Math.floor(SC.idt.b1_t * 100 / c) / 100)  + ' msec') ;
  ret.push('EXT: ' + SC.idt.ext_c + ' (avg time: ' + (Math.floor(SC.idt.ext_t * 100 / SC.idt.ext_c) / 100)  + ' msec)') ;
  ret.push('VIEWS: ' + v + ' (avg time: ' + (Math.floor(SC.idt.v_t * 100 / v) / 100)  + ' msec)') ;
  ret.push('VIEW CREATE: ' + (Math.floor(SC.idt.vc_t * 100 / v) / 100)  + ' msec)') ;
  console.log(ret.join("\n")) ;
  return ret.join("\n") ;
} ;

SC.Object.prototype = {
  
  isObject: true,
  
  init: function() {
    
    var keySource = this.viewType || this ; 
    var loc ; var keys ; var key ; var value ; 
    
    var r = SC.idt.active ; var idtStart ; var idtSt ;
    if (r) {
      SC.idt.count++;
      idtStart = new Date().getTime() ;
    } ;
    
    if (keys = keySource._observers) for(loc=0;loc<keys.length;loc++) {
      key = keys[loc] ; value = this[key] ;

      if (r) {
        SC.idt.keys++ ;
        SC.idt.observers++ ;
        idtSt = new Date().getTime() ;
      }
      
      var propertyPaths = null ;
      if ((value instanceof Function) && value.propertyPaths) {
        propertyPaths = value.propertyPaths ;
        value = value.bind(this) ;
      } else if (typeof(value) == "string") {
        propertyPaths = [value] ;
        value = this.propertyObserver.bind(this,key.slice(0,-8)) ;
      }
      
      if (propertyPaths) for(var ploc=0;ploc<propertyPaths.length;ploc++) { 
        var propertyPath = propertyPaths[ploc] ;
        var object = null;  
        
        if (propertyPath.indexOf('.') == -1) {
          this.addObserver(propertyPath, value) ;
          
        } else switch(propertyPath.slice(0,1)) {
          case '*':
          case '.':
            propertyPath = propertyPath.slice(1,propertyPath.length) ;
            this.addObserver(propertyPath, value) ;
            break ;
            
          default:
            SC.Observers.addObserver(propertyPath, value) ;
        }
      }
      
      if (r) SC.idt.observers_t += (new Date().getTime()) - idtSt ;
    }

    this.bindings = [] ;
    if (keys = keySource._bindings) for(loc=0;loc<keys.length;loc++) {
      key = keys[loc] ; value = this[key] ;

      if (r) {
        SC.idt.keys++ ;
        SC.idt.bindings++ ;
        idtSt = new Date().getTime() ;
      }
        
      var propertyKey = key.slice(0,-7) ; // contentBinding => content
      var relay ;
      var props = { to: [this,propertyKey] } ;

      if ((typeof(value) == "string") || (value instanceof Array)) {
        relay = this[propertyKey + 'BindingDefault'] || SC.Binding.From;
        relay = relay(value) ;
      } else relay = value ;

      var relayFrom = relay.prototype.from ;
      if (typeof(relayFrom) == "string") switch(relayFrom.slice(0,1)) {
        case '*':
        case '.':
          relayFrom = [this,relayFrom.slice(1,relayFrom.length)];
      }        

      if(r) bt = new Date().getTime();

      relay = relay.create(props, { from: relayFrom }) ;
      this[key] = relay ;
      this.bindings.push(relay) ;

      if (r) SC.idt.b1_t += (new Date().getTime()) - bt ;
      if (r) SC.idt.bindings_t += (new Date().getTime()) - idtSt ;      
    }

    if (keys = keySource._properties) for(loc=0;loc<keys.length;loc++) {
      key = keys[loc] ; value = this[key] ;
      if (value && value.dependentKeys && (value.dependentKeys.length > 0)) {
        args = value.dependentKeys.slice() ;
        args.unshift(key) ;
        this.registerDependentKey.apply(this,args) ;
      }
    }

    if (r) { SC.idt.t += ((new Date().getTime()) - idtStart); }
  },
  
  mixin: function() { return SC.Object.mixin.apply(this,arguments) ; },

  keys: function(all) {
    var ret = []; for(var key in this) { 
      if (all || ret.hasOwnProperty(key)) ret.push(key); 
    }; return ret ;  
  },

  didChangeFor: function(context) {    
    var keys = $A(arguments) ;
    context = keys.shift() ;
    
    var ret = false ;
    if (!this._didChangeCache) this._didChangeCache = {} ;
    var seen = this._didChangeCache[context] || {} ;
    var loc = keys.length ;
    while(--loc >= 0) {
      var key = keys[loc] ;
      var val = this.get(key) ;
      if (seen[key] != val) ret = true ;
      seen[key] = val ;
    }
    
    this._didChangeCache[context] = seen ;
    return ret ;
  },
  
  
  awake: function(key) { 
    if (key !== undefined) {
      var obj = this.outlet(key) ;
      if (obj) obj.awake() ;
      return ;
    }

    if (this._awake) return ;
    this._awake = true ;
    
    this.bindings.invoke('relay') ; 
    
    if (this.outlets && this.outlets.length) {
      var stack = [] ;
      var working = [this, this.outlets.slice()] ;
      while(working) {
        var next = working[1].pop() ;
        var obj = working[0] ;
        
        if (next) {
          next = obj[next] ;
          if (next) {
            if (next.bindings) next.bindings.invoke('relay') ;
            
            if (next.outlets && next.outlets.length > 0) {
              stack.push(working) ;
              working = [next,next.outlets.slice()] ;
            }
          }
          
        } else working = stack.pop() ;
      }
    }
    
  },
  
  outlets: [],

  outlet: function(key) {
    var value = this[key] ; // get the current value.

    if (value && (value instanceof Function) && value.isOutlet == true) {
      if (!this._originalOutlets) this._originalOutlets = {} ;
      this._originalOutlets[key] = value ;

      value = value.call(this) ;
      this.set(key, value) ;
    } else if (typeof(value) == "string") {
      if (!this._originalOutlets) this._originalOutlets = {} ;
      this._originalOutlets[key] = value ;

      value = (this.$$sel) ? this.$$sel(value) : $$sel(value) ;
      if (value) value = (value.length > 0) ? ((value.length == 1) ? value[0] : value) : null ;
      this.set(key, value) ;
    }

    return value ;
  },
  

  get: function(key) {
    var ret = this[key] ;
    if (ret === undefined) {
      return this.unknownProperty(key) ;
    } else if (ret && (ret instanceof Function) && ret.isProperty) {
      return ret.call(this,key) ;
    } else return ret ;
  },

  set: function(key, value) {
    var func = this[key] ;
    var ret = value ;

    this.propertyWillChange(key) ;

    if (func && (func instanceof Function) && (func.isProperty)) {
      ret = func.call(this,key,value) ;
    } else if (func === undefined) {
      ret = this.unknownProperty(key,value) ;
    } else ret = this[key] = value ;

    this.propertyDidChange(key, ret) ;
    return ret ;
  },  

  incrementProperty: function(key) { 
    return this.set(key,(this.get(key) || 0)+1); 
  },

  decrementProperty: function(key) {
    return this.set(key,(this.get(key) || 0) - 1 ) ;
  },

  toggleProperty: function(key,value,alt) { 
    if (value === undefined) value = true ;
    if (alt == undefined) alt = false ;
    value = (this.get(key) == value) ? alt : value ;
    this.set(key,value);
  },

  unknownProperty: function(key,value) {
    if (!(value === undefined)) { this[key] = value; }
    return value ;
  },

  propertyObserver: function(observer,target,key,value) {},

  beginPropertyChanges: function() {
    this._kvo().changes++ ;
  },

  endPropertyChanges: function() {
    var kvo = this._kvo() ;  kvo.changes--;
    if (kvo.changes <= 0) this._notifyPropertyObservers() ;
  },

  propertyWillChange: function(key) {
    this._kvo().changes++ ;
  },

  propertyDidChange: function(key,value) {
    this._kvo().changed[key] = value ;
    var kvo = this._kvo() ;  kvo.changes--;
    if (kvo.changes <= 0) this._notifyPropertyObservers() ;
  },

  allPropertiesDidChange: function() {
    this._notifyPropertyObservers(true) ;
  },

  addObserver: function(key,func) {
    var kvo = this._kvo() ;

    var parts = key.split('.') ;
    if (parts.length > 1) {
      var co = SC._ChainObserver.createChain(this,parts,func) ;
      co.masterFunc = func ;
      var chainObservers = kvo.chainObservers[key] || [] ;
      chainObservers.push(co) ;
      kvo.chainObservers[key] = chainObservers ;

    } else {      
      var observers = kvo.observers[key] = (kvo.observers[key] || []) ;
      var found = false; var loc = observers.length;
      while(!found && --loc >= 0) found = (observers[loc] == func) ;
      if (!found) observers.push(func) ;
    }

  },

  addProbe: function(key) { this.addObserver(key,logChange); },
  removeProbe: function(key) { this.removeObserver(key,logChange); },

  observeOnce: function(key, func, timeout) {
    var timeoutObject = null ;
    var target = this ;
    var handler = function(theTarget,theKey,theValue,didTimeout) {
      func(theTarget,theKey,theValue,didTimeout) ;
      target.removeObserver(key,handler) ;
      if (timeoutObject) { clearTimeout(timeoutObject); }
    } ;

    target.addObserver(key,handler) ;
    if (timeout) timeoutObject = setTimeout(function() {
      handler(target,key,target.get(key),true) ;
    }, timeout) ;

    handler.cancel = function() { target.removeObserver(key,handler); } ;
    return handler ;
  },

  removeObserver: function(key,func) {
    var kvo = this._kvo() ;

    var parts = key.split('.') ;
    if (parts.length > 1) {
      var chainObservers = kvo.chainObserver[key] || [] ;
      var newObservers = [] ;
      chainObservers.each(function(co) {
        if (co.masterFunc != func) newObservers.push(co) ;
      }) ;
      kvo.chainObservers[key] = newObservers ;

    } else {
      var observers = kvo.observers[key] || [] ;
      observers = observers.without(func) ;
      kvo.observers[key] = observers ;
    }
  },

  registerDependentKey: function(key) {
    var keys = $A(arguments) ;
    var dependent = keys.shift() ;
    var kvo = this._kvo() ;
    for(var loc=0;loc<keys.length;loc++) {
    	var key = keys[loc];
    	if (key instanceof Array) {
    		key.push(dependent) ;
    		this.registerDependentKey.apply(this,key) ;
    	} else {
				var dependents = kvo.dependents[key] || [] ;
				dependents.push(dependent) ;
				kvo.dependents[key] = dependents ;
    	}
    }
  },


  _kvo: function() {
    if (!this._kvod) this._kvod = { 
      changes: 0, changed: {}, observers: {}, dependents: {},
      chainObservers: {}
    } ;
    return this._kvod ;
  },

  propertyRevision: 1,
  
  _notifyPropertyObservers: function(allObservers) {
    var key ; var observers ; var keys = [] ;
    var loc; var oloc ; var value ;
    var kvo = this._kvo() ;

    SC.Observers.flush() ; // hookup as many observers as possible.

    this.propertyRevision++;
    
    var keySource = (allObservers) ? kvo.observers : kvo.changed ;
    for(key in keySource) { 
      keys.push(key); 
      if (!allObservers) {
        var dependents = kvo.dependents[key] ;
        if (dependents) keys.push(dependents) ;
      }
    }

    keys = keys.flatten() ;
    var starObservers = kvo.observers['*'] ;

    var changed = kvo.changed ; kvo.changed = {} ;

    var target = this ; 
    loc = keys.length ;
    
    var notifiedKeys = {} ; // avoid duplicates.
    while(--loc >= 0) {
      key = keys[loc] ; observers = kvo.observers[key] ;
      if (!notifiedKeys[key]) {
        notifiedKeys[key] = key ;
        value = (allObservers || (!changed[key])) ? this.get(key) : changed[key];

        if (starObservers) {
          observers = (observers) ? observers.concat(starObservers) : starObservers ;  
        }

        if (observers) {
          oloc = observers.length ;
          var args = [target, key, value, this.propertyRevision] ;
          while(--oloc >= 0) {
            var observer = observers[oloc] ;
            SC.NotificationQueue.add(null, observer, args) ;
            try {
            }
            catch(e) {
              console.log("Exception while notify("+key+"): " + e) ;
            } // catch
          } // while(oloc)
        } // if (observers) 

        if (this.propertyObserver != SC.Object.prototype.propertyObserver) {
          SC.NotificationQueue.add(this, this.propertyObserver,
            [null, target, key, value, this.propertyRevision]) ;
        }
      }

    } // while(--loc)

    SC.NotificationQueue.flush() ; 
  },

  invokeLater: function(period) {
    var args = $A(arguments) ; args.unshift(this) ;
    return SC.runLoop.schedule.apply(SC.runLoop,args) ; 
  },
  
  _cprops: ['_cprops','outlets','_bindings','_observers','_properties']  

} ;

SC._nextGUID = 0 ;

function logChange(target,key,value) {
  console.log("CHANGE: " + target + "["+key+"]=" + value) ;
}

Object.extend(Function.prototype,{
  
  property: function() {
    this.dependentKeys = $A(arguments) ; 
    this.isProperty = true; return this; 
  },
  
  observes: function(propertyPaths) { 
    this.propertyPaths = $A(arguments); 
    return this;
  },
  
  typeConverter: function() {
    this.isTypeConverter = true; return this ;
  }
  
}) ;

SC.Observers = {
  queue: {},
  
  addObserver: function(propertyPath, func) {
    if (typeof(propertyPath) == "string") {
      var tuple = SC.Object.tupleForPropertyPath(propertyPath) ;
    } else {
      var tuple = propertyPath; 
    }
    
    if (tuple) {
      tuple[0].addObserver(tuple[1],func) ;
    } else {
      var ary = this.queue[propertyPath] || [] ;
      ary.push(func) ;
      this.queue[propertyPath] = ary ;
    }
  },
  
  removeObserver: function(propertyPath, func) {
    var tuple = SC.Object.tupleForPropertyPath(propertyPath) ;
    if (tuple) {
      tuple[0].removeObserver(tuple[1],func) ;
    }
    
    var ary = this.queue[propertyPath] ;
    if (ary) {
      ary = ary.without(func) ;
      this.queue[propertyPath] = ary ;
    }
  },
  
  flush: function() {
    var newQueue = {} ;
    for(var path in this.queue) {
      var funcs = this.queue[path] ;
      var tuple = SC.Object.tupleForPropertyPath(path) ;
      if (tuple) {
        var loc = funcs.length ;
        while(--loc >= 0) {
          var func = funcs[loc] ;
          tuple[0].addObserver(tuple[1],func) ;
        }
      } else newQueue[path] = funcs ;
    }
    
    this.queue = newQueue ; 
  }
} ;

SC.NotificationQueue = {
  queue: [],
  maxFlush: 5000, // max time you can spend flushing before we reschedule.
  _flushing: false,
  add: function(target, func, args) { this.queue.push([target, func, args]);},
  flush: function() {
    if (this._flushing) return ;
    this._flushing = true ;
    
    var start = new Date().getTime() ;
    var now = start ;
    var n = null ;
    while(((now - start) < this.maxFlush) && (n = this.queue.pop())) { 
      try {
        var t = n[0] || n[1] ;
        n[1].apply(t,n[2]) ;
      }
      catch(e) {
        console.log("Exception while notify("+n[1]+"): " + e) ;
      } // catch  
      now = new Date().getTime() ;
    }
    this._flushing = false ;
    if (this.queue.length > 0) { setTimeout(this._reflush,1); }
  },
  
  _reflush: function() { SC.NotificationQueue.flush(); }
} ;


SC._ChainObserver = SC.Object.extend({
  
  isChainObserver: true,
  
  target: null,
    
  property: null,
    
  next: null,
    
  func: null,
    
  propertyObserver: function(observing,target,key,value) {
    if ((key == 'target') && (value != this._target)) {
      var func = this.boundObserver() ;
      if (this._target && this._target.removeObserver) {
        this._target.removeObserver(this.property,func);
      } 
      this._target = value ;
      if (this._target && this._target.addObserver) {
        this._target.addObserver(this.property,func) ;
      }
      
      if (!(observing == 'init')) this.targetPropertyObserver() ;
    }
  },
  
  boundObserver: function() {
    if (!this._boundObserver) {
      this._boundObserver = this.targetPropertyObserver.bind(this) ;
    }
    return this._boundObserver ;
  },
  
  targetPropertyObserver: function() {
    
    var value = (this.target && this.target.get && this.property) ? this.target.get(this.property) : null ;

    if (value !== this._lastTargetProperty) {
      this._lastTargetProperty = value ;
      if (this.next) {
        this.next.set('target',value) ;
        
      } else if (this.func) this.func(this.target,this.property,value) ;
    }
    
  },
  
  init: function() {
    arguments.callee.base.call(this) ;
    this.propertyObserver('init',this,'target',this.get('target')) ;
  }
    
}) ;

SC._ChainObserver.mixin({
  createChain: function(target,keys,func) {
    var property = keys.shift() ;
    var nextTarget = (target && property && target.get) ? target.get(property) : null ;
    var next = (keys && keys.length>0) ? this.createChain(nextTarget,keys,func) : null ;
    
    return this.create({
      target: target, property: property,
      next: next, func: ((next) ? null : func)        
    }) ;
  }
}) ;




require('foundation/object') ;

SC.ObjectController = SC.Object.extend({
  
  content: null,  
  
  isEditable: true, isEnabled: true,
  
  hasNoContent: true,
  hasSingleContent: false, 
  hasMultipleContent: false,

  autocommit: false,
  
  
  refresh: function() {
    var content = this.get('content') ;
    if (content && !(content instanceof Array)) content = [content]
    if (content) content.invoke('refresh') ;
    return content != null ;
  },
  
  commit: function() {
    var content = this.get('content') ;
    if (this.commitNotice) Notice.show(this.commitNotice.loc()) ;
    if (content) {
      if (!(content instanceof Array)) content = [content]; 
      content.invoke('commit') ;
      return true ;
    }
    return false ;
  },

  commitNotice: null,
  
  destroy: function() {
    var content = this.get('content') ;
    if (!content) return false;

    if (this.deleteConfirm && !confirm(this.deleteConfirm.loc())) return NO;
    if (this.deleteNotice) Notice.show(this.deleteNotice.loc()) ;
    if (!(content instanceof Array)) content = [content] ;
    content.invoke('destroy') ;
    this.set('content',[]) ;
    return true ;
  },
  
  deleteNotice: null,
  deleteConfirm: null,
  
  
  unknownProperty: function(key,value) {
    if (key == "content") {
      if (!(value === undefined)) this[key] = value ;
      return this[key] ;
    } else {
      var obj = this.get('content') ;
      if (obj) {
        var isArray = obj instanceof Array ;
        if (isArray && obj.length == 1) {
          obj = obj[0]; isArray = false ;
        }
        
        if (value !== undefined) this._needsCommit = this.get('autocommit') ;

        if (isArray) {
          return (value === undefined) ? obj.invoke('get',key) : obj.invoke('set',key,value) ;
        } else {
          return (value === undefined) ? obj.get(key) : obj.set(key, value) ;
        }

      } else return null ;
    }
  },

  _lastPropertyRevision: 0,
  
  propertyObserver: function(observer,target,key,value,propertyRevision) {
    
    if (propertyRevision <= this._lastPropertyRevision) return ;
    this._lastPropertyRevision = propertyRevision;
    
    var isNewContent = (target == this) && (key == "content") && (value != this._content) ;
    if (isNewContent && value && value.isEqual) {
      isNewContent = !(value.isEqual(this._content)) ;
    }
    
    if (isNewContent) {
      
      if (!this._boundObserver) {
        this._boundObserver = this._contentObserver.bind(this) ;
      }
      
      if (this._content) {
        var objects = this._content ;
        objects = (objects instanceof Array) ? objects : [objects] ;
        var loc = objects.length;
        while(--loc >= 0) {
          var obj = objects[loc] ;
          if (obj && obj.removeObserver) obj.removeObserver('*',this._boundObserver) ;
        }
      } 
      
      this._content = value ;
      if (this._content) {
        var objects = this._content ;
        objects = (objects instanceof Array) ? objects : [objects] ;
        while(--loc >= 0) {
          var obj = objects[loc] ;
          if (obj && obj.addObserver) obj.addObserver('*',this._boundObserver) ;
        }
      }

      var count = (this._content) ? ((this._content instanceof Array) ? this._content.length : 1) : 0 ;
      this.set('hasNoContent',count == 0) ;
      this.set('hasSingleContent',count == 1) ;
      this.set('hasMultipleContent',count > 1) ;
      
      this.allPropertiesDidChange() ;
      
    } else if ((target == this) && (this._needsCommit)) {
      this.commit(); this._needsCommit = false ;
    }
  },
  
  _contentObserver: function(target,key,value) {
    this._changeFromContent = true ;
    if (key == '*') {
      this.allPropertiesDidChange() ;
    } else {
      this.propertyWillChange(key) ;
      this.propertyDidChange(key,value) ;
    }
    this._changeFromContent = false ;
  }
  
}) ;


require('controllers/object') ;

SC.CollectionController = SC.ObjectController.extend({


  arrangedObjects: [],
  
  selection: function(key,value) {
    if (value !== undefined) {
      
      value = (value) ? ((value instanceof Array) ? value : [value]) : [] ;
      
      var allowed = this.get('allowsSelection') ;
      if (allowed && (value.length == 0)) allowed = this.get('allowsEmptySelection') ;
      if (allowed && (value.length > 1)) allowed = this.get('allowsMultipleSelection') ;
      
      if (allowed) {
        if (this._editingNewRecord) setTimeout(this._newRecordDidLoseFocus.bind(this,this._editingNewRecord),1) ;
        this._editingNewRecord = null ;
        this._selection = value ;
      }
    }
    return this._selection ;
  }.property(),
  
  allowsSelection: true,
  
  allowsMultipleSelection: true,
  
  allowsEmptySelection: true,
  
  canEditCollection: false,

  pageSize: 0,
  
  pageCount: function() {
    var pageSize = this.get('pageSize') ;
    if (pageSize <= 0) return 1 ;
    
    var content = this.get('content') ;
    var count = (content && content.get) ? content.get('count') : 0 ;
    if (count === null) count = 0 ;
    return Math.ceil(count / pageSize) ;
  }.property(),
  
  currentPage: function(key, value) {
    if (value !== undefined) {
      if (this._currentPage != value) {
        var pc = Math.max(this.get('pageCount')-1,0);
        if (value > pc) value = pc ;
        if (value < 0) value = 0 ;
        this._currentPage = value ;
      }
    }
    return this._currentPage || 0 ;  
  }.property(),
  

  newObject: function(settings) {
    var content = this.get('content') ;
    if (!content || !this.get('canEditCollection')) return ; // only if allowed 
    try {
      if (content.newRecord) { 
        var rec = content.newRecord(settings) ;
        var controller = this ;
        setTimeout(function() {
          controller.set('selection',(rec) ? [rec] : []) ;
          controller._editingNewRecord = rec ;
        },1) ;        
      }
    }   
    catch (e) {
    }
  },

  addObjects: function(objects) {
    var content = this.get('content') ;
    if (!content || !this.get('canEditCollection')) return ; // only if allowed 
    try {
      objects = $A(arguments).flatten() ;
      if (content.addRecords) { 
        content.addRecords(objects) ;
        this.set('selection',(objects) ? objects : []) ;
      }
    }   
    catch (e) {
    }
  },
  
  addSelection: function() { 
    return this.addObjects(this.get('selection'));
  },
  
  removeObjects: function(objects) {
    var content = this.get('content') ;
    if (!content || !this.get('canEditCollection')) return ; // only if allowed 
    try {
      objects = $A(arguments).flatten() ;
      if (content.removeRecords) { 
        var rec = content.removeRecords(objects) ;
        var sel = (this.get('selection') || []).without(objects) ;
        this.set('selection',(sel) ? sel : []) ;
      }
    }   
    catch (e) {
    }
  },

  removeSelection: function() {
    return this.removeObjects(this.get('selection')) ;
  },

  newObjectDidLoseFocus: function(rec) { rec.destroy() ; },
  
  
  _newRecordDidLoseFocus: function(rec) {
    if (rec.get('newRecord')) this.newObjectDidLoseFocus(rec) ;
  },
  
  _pageObserver: function() {
    var content = this.get('content') ; 
    if (content instanceof Array) content = content[0] ;
    if (!content) return ; 
    
    var curOffset = content.get('offset') || 0 ;
    var curLimit = content.get('limit') || 0 ;
    var count = content.get('count') || 0 ;

    var currentPage = this.get('currentPage') ;
    var pageSize = this.get('pageSize') ;
    var newOffset, newLimit ;
    if (pageSize == 0) {
      newOffset = 0; newLimit = 0 ;
    } else {
      newOffset = currentPage * pageSize ;
      newLimit = pageSize ;      
    }
    
    if ((newOffset != curOffset) || (newLimit != curLimit)) {
      content.beginPropertyChanges() ;
      content.set('offset',newOffset) ;
      content.set('limit',newLimit) ;
      content.endPropertyChanges() ;
    }    
  }.observes('currentPage','pageCount','pageSize'),
  
  _recordsObserver: function(target,key,value) {
    var old = this.get('arrangedObjects') ;
    value = Array.asArray(target.get(key)) ;
    if (!old.isEqual(value)) {
       this.set('arrangedObjects',value.slice()) ;
      var objects = this.get('arrangedObjects') || [] ;
      if (!(objects instanceof Array)) objects = [objects] ;
      
      var currentSelection = this.get('selection') || [] ;
      var sel = [] ;

      currentSelection.each(function(obj) {
        if (objects.include(obj)) sel.push(obj) ;
      }) ;
      
      if (!this.allowsSelection) {
        sel = [] ;
      }
      
      if ((sel.length>1) && !this.allowsMultipleSelection) {
        sel = [sel[0]] ;
      }
      
      if ((sel.length == 0) && !this.allowsEmptySelection) {
        if (objects.length > 0) sel = [objects[0]] ;
      }
      
      this.set('selection',sel) ;

    }
  }.observes('records') 
  
}) ;

/*  
	Animator.js 1.1.9
	
	This library is released under the BSD license:

	Copyright (c) 2006, Bernard Sumption. All rights reserved.
	
	Redistribution and use in source and binary forms, with or without
	modification, are permitted provided that the following conditions are met:
	
	Redistributions of source code must retain the above copyright notice, this
	list of conditions and the following disclaimer. Redistributions in binary
	form must reproduce the above copyright notice, this list of conditions and
	the following disclaimer in the documentation and/or other materials
	provided with the distribution. Neither the name BernieCode nor
	the names of its contributors may be used to endorse or promote products
	derived from this software without specific prior written permission. 
	
	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
	AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
	IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
	ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
	ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
	DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
	SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
	CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
	LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
	OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
	DAMAGE.

*/


function Animator(options) {
	this.setOptions(options);
	var _this = this;
	this.timerDelegate = function(){_this.onTimerEvent()};
	this.subjects = [];
	this.target = 0;
	this.state = 0;
	this.lastTime = null;
};
Animator.prototype = {
	setOptions: function(options) {
		this.options = Animator.applyDefaults({
			interval: 20,  // time between animation frames
			duration: 400, // length of animation
			onComplete: function(){},
			onStep: function(){},
			transition: Animator.tx.easeInOut
		}, options);
	},
	seekTo: function(to) {
		this.seekFromTo(this.state, to);
	},
	seekFromTo: function(from, to) {
		this.target = Math.max(0, Math.min(1, to));
		this.state = Math.max(0, Math.min(1, from));
		this.lastTime = new Date().getTime();
		if (!this.intervalId) {
			this.intervalId = window.setInterval(this.timerDelegate, this.options.interval);
		}
	},
	jumpTo: function(to) {
		this.target = this.state = Math.max(0, Math.min(1, to));
		this.propagate();
	},
	toggle: function() {
		this.seekTo(1 - this.target);
	},
	addSubject: function(subject) {
		this.subjects[this.subjects.length] = subject;
		return this;
	},
	clearSubjects: function() {
		this.subjects = [];
	},
	propagate: function() {
		var value = this.options.transition(this.state);
		for (var i=0; i<this.subjects.length; i++) {
			if (this.subjects[i].setState) {
				this.subjects[i].setState(value);
			} else {
				this.subjects[i](value);
			}
		}
	},
	onTimerEvent: function() {
		var now = new Date().getTime();
		var timePassed = now - this.lastTime;
		this.lastTime = now;
		var movement = (timePassed / this.options.duration) * (this.state < this.target ? 1 : -1);
		if (Math.abs(movement) >= Math.abs(this.state - this.target)) {
			this.state = this.target;
		} else {
			this.state += movement;
		}
		
		try {
			this.propagate();
		} finally {
			this.options.onStep.call(this);
			if (this.target == this.state) {
				window.clearInterval(this.intervalId);
				this.intervalId = null;
				this.options.onComplete.call(this);
			}
		}
	},
	play: function() {this.seekFromTo(0, 1)},
	reverse: function() {this.seekFromTo(1, 0)},
	inspect: function() {
		var str = "#<Animator:\n";
		for (var i=0; i<this.subjects.length; i++) {
			str += this.subjects[i].inspect();
		}
		str += ">";
		return str;
	}
}
Animator.applyDefaults = function(defaults, prefs) {
	prefs = prefs || {};
	var prop, result = {};
	for (prop in defaults) result[prop] = prefs[prop] !== undefined ? prefs[prop] : defaults[prop];
	return result;
}
Animator.makeArray = function(o) {
	if (o == null) return [];
	if (!o.length) return [o];
	var result = [];
	for (var i=0; i<o.length; i++) result[i] = o[i];
	return result;
}
Animator.camelize = function(string) {
	var oStringList = string.split('-');
	if (oStringList.length == 1) return oStringList[0];
	
	var camelizedString = string.indexOf('-') == 0
		? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
		: oStringList[0];
	
	for (var i = 1, len = oStringList.length; i < len; i++) {
		var s = oStringList[i];
		camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
	}
	return camelizedString;
}
Animator.apply = function(el, style, options) {
	if (style instanceof Array) {
		return new Animator(options).addSubject(new CSSStyleSubject(el, style[0], style[1]));
	}
	return new Animator(options).addSubject(new CSSStyleSubject(el, style));
}
Animator.makeEaseIn = function(a) {
	return function(state) {
		return Math.pow(state, a*2); 
	}
}
Animator.makeEaseOut = function(a) {
	return function(state) {
		return 1 - Math.pow(1 - state, a*2); 
	}
}
Animator.makeElastic = function(bounces) {
	return function(state) {
		state = Animator.tx.easeInOut(state);
		return ((1-Math.cos(state * Math.PI * bounces)) * (1 - state)) + state; 
	}
}
Animator.makeADSR = function(attackEnd, decayEnd, sustainEnd, sustainLevel) {
	if (sustainLevel == null) sustainLevel = 0.5;
	return function(state) {
		if (state < attackEnd) {
			return state / attackEnd;
		}
		if (state < decayEnd) {
			return 1 - ((state - attackEnd) / (decayEnd - attackEnd) * (1 - sustainLevel));
		}
		if (state < sustainEnd) {
			return sustainLevel;
		}
		return sustainLevel * (1 - ((state - sustainEnd) / (1 - sustainEnd)));
	}
}
Animator.makeBounce = function(bounces) {
	var fn = Animator.makeElastic(bounces);
	return function(state) {
		state = fn(state); 
		return state <= 1 ? state : 2-state;
	}
}
 
Animator.tx = {
	easeInOut: function(pos){
		return ((-Math.cos(pos*Math.PI)/2) + 0.5);
	},
	linear: function(x) {
		return x;
	},
	easeIn: Animator.makeEaseIn(1.5),
	easeOut: Animator.makeEaseOut(1.5),
	strongEaseIn: Animator.makeEaseIn(2.5),
	strongEaseOut: Animator.makeEaseOut(2.5),
	elastic: Animator.makeElastic(1),
	veryElastic: Animator.makeElastic(3),
	bouncy: Animator.makeBounce(1),
	veryBouncy: Animator.makeBounce(3)
}

function NumericalStyleSubject(els, property, from, to, units) {
	this.els = Animator.makeArray(els);
	if (property == 'opacity' && window.ActiveXObject) {
		this.property = 'filter';
	} else {
		this.property = Animator.camelize(property);
	}
	this.from = parseFloat(from);
	this.to = parseFloat(to);
	this.units = units != null ? units : 'px';
}
NumericalStyleSubject.prototype = {
	setState: function(state) {
		var style = this.getStyle(state);
		var visibility = (this.property == 'opacity' && state == 0) ? 'hidden' : '';
		var j=0;
		for (var i=0; i<this.els.length; i++) {
			try {
				this.els[i].style[this.property] = style;
			} catch (e) {
				if (this.property != 'fontWeight') throw e;
			}
			if (j++ > 20) return;
		}
	},
	getStyle: function(state) {
		state = this.from + ((this.to - this.from) * state);
		if (this.property == 'filter') return "alpha(opacity=" + Math.round(state*100) + ")";
		if (this.property == 'opacity') return state;
		return Math.round(state) + this.units;
	},
	inspect: function() {
		return "\t" + this.property + "(" + this.from + this.units + " to " + this.to + this.units + ")\n";
	}
}

function ColorStyleSubject(els, property, from, to) {
	this.els = Animator.makeArray(els);
	this.property = Animator.camelize(property);
	this.to = this.expandColor(to);
	this.from = this.expandColor(from);
	this.origFrom = from;
	this.origTo = to;
}

ColorStyleSubject.prototype = {
	expandColor: function(color) {
		var hexColor, red, green, blue;
		hexColor = ColorStyleSubject.parseColor(color);
		if (hexColor) {
			red = parseInt(hexColor.slice(1, 3), 16);
			green = parseInt(hexColor.slice(3, 5), 16);
			blue = parseInt(hexColor.slice(5, 7), 16);
			return [red,green,blue]
		}
		if (window.DEBUG) {
			alert("Invalid colour: '" + color + "'");
		}
	},
	getValueForState: function(color, state) {
		return Math.round(this.from[color] + ((this.to[color] - this.from[color]) * state));
	},
	setState: function(state) {
		var color = '#'
				+ ColorStyleSubject.toColorPart(this.getValueForState(0, state))
				+ ColorStyleSubject.toColorPart(this.getValueForState(1, state))
				+ ColorStyleSubject.toColorPart(this.getValueForState(2, state));
		for (var i=0; i<this.els.length; i++) {
			this.els[i].style[this.property] = color;
		}
	},
	inspect: function() {
		return "\t" + this.property + "(" + this.origFrom + " to " + this.origTo + ")\n";
	}
}

ColorStyleSubject.parseColor = function(string) {
	var color = '#', match;
	if(match = ColorStyleSubject.parseColor.rgbRe.exec(string)) {
		var part;
		for (var i=1; i<=3; i++) {
			part = Math.max(0, Math.min(255, parseInt(match[i])));
			color += ColorStyleSubject.toColorPart(part);
		}
		return color;
	}
	if (match = ColorStyleSubject.parseColor.hexRe.exec(string)) {
		if(match[1].length == 3) {
			for (var i=0; i<3; i++) {
				color += match[1].charAt(i) + match[1].charAt(i);
			}
			return color;
		}
		return '#' + match[1];
	}
	return false;
}
ColorStyleSubject.toColorPart = function(number) {
	if (number > 255) number = 255;
	var digits = number.toString(16);
	if (number < 16) return '0' + digits;
	return digits;
}
ColorStyleSubject.parseColor.rgbRe = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i;
ColorStyleSubject.parseColor.hexRe = /^\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;

function DiscreteStyleSubject(els, property, from, to, threshold) {
	this.els = Animator.makeArray(els);
	this.property = Animator.camelize(property);
	this.from = from;
	this.to = to;
	this.threshold = threshold || 0.5;
}

DiscreteStyleSubject.prototype = {
	setState: function(state) {
		var j=0;
		for (var i=0; i<this.els.length; i++) {
			this.els[i].style[this.property] = state <= this.threshold ? this.from : this.to; 
		}
	},
	inspect: function() {
		return "\t" + this.property + "(" + this.from + " to " + this.to + " @ " + this.threshold + ")\n";
	}
}

function CSSStyleSubject(els, style1, style2) {
	els = Animator.makeArray(els);
	this.subjects = [];
	if (els.length == 0) return;
	var prop, toStyle, fromStyle;
	if (style2) {
		fromStyle = this.parseStyle(style1, els[0]);
		toStyle = this.parseStyle(style2, els[0]);
	} else {
		toStyle = this.parseStyle(style1, els[0]);
		fromStyle = {};
		for (prop in toStyle) {
			fromStyle[prop] = CSSStyleSubject.getStyle(els[0], prop);
		}
	}
	var prop;
	for (prop in fromStyle) {
		if (fromStyle[prop] == toStyle[prop]) {
			delete fromStyle[prop];
			delete toStyle[prop];
		}
	}
	var prop, units, match, type, from, to;
	for (prop in fromStyle) {
		var fromProp = String(fromStyle[prop]);
		var toProp = String(toStyle[prop]);
		if (toStyle[prop] == null) {
			if (window.DEBUG) alert("No to style provided for '" + prop + '"');
			continue;
		}
		
		if (from = ColorStyleSubject.parseColor(fromProp)) {
			to = ColorStyleSubject.parseColor(toProp);
			type = ColorStyleSubject;
		} else if (fromProp.match(CSSStyleSubject.numericalRe)
				&& toProp.match(CSSStyleSubject.numericalRe)) {
			from = parseFloat(fromProp);
			to = parseFloat(toProp);
			type = NumericalStyleSubject;
			match = CSSStyleSubject.numericalRe.exec(fromProp);
			var reResult = CSSStyleSubject.numericalRe.exec(toProp);
			if (match[1] != null) {
				units = match[1];
			} else if (reResult[1] != null) {
				units = reResult[1];
			} else {
				units = reResult;
			}
		} else if (fromProp.match(CSSStyleSubject.discreteRe)
				&& toProp.match(CSSStyleSubject.discreteRe)) {
			from = fromProp;
			to = toProp;
			type = DiscreteStyleSubject;
			units = 0;   // hack - how to get an animator option down to here
		} else {
			if (window.DEBUG) {
				alert("Unrecognised format for value of "
					+ prop + ": '" + fromStyle[prop] + "'");
			}
			continue;
		}
		this.subjects[this.subjects.length] = new type(els, prop, from, to, units);
	}
}

CSSStyleSubject.prototype = {
	parseStyle: function(style, el) {
		var rtn = {};
		if (style.indexOf(":") != -1) {
			var styles = style.split(";");
			for (var i=0; i<styles.length; i++) {
				var parts = CSSStyleSubject.ruleRe.exec(styles[i]);
				if (parts) {
					rtn[parts[1]] = parts[2];
				}
			}
		}
		else {
			var prop, value, oldClass;
			oldClass = el.className;
			el.className = style;
			for (var i=0; i<CSSStyleSubject.cssProperties.length; i++) {
				prop = CSSStyleSubject.cssProperties[i];
				value = CSSStyleSubject.getStyle(el, prop);
				if (value != null) {
					rtn[prop] = value;
				}
			}
			el.className = oldClass;
		}
		return rtn;
		
	},
	setState: function(state) {
		for (var i=0; i<this.subjects.length; i++) {
			this.subjects[i].setState(state);
		}
	},
	inspect: function() {
		var str = "";
		for (var i=0; i<this.subjects.length; i++) {
			str += this.subjects[i].inspect();
		}
		return str;
	}
}
CSSStyleSubject.getStyle = function(el, property){
	var style;
	if(document.defaultView && document.defaultView.getComputedStyle){
		style = document.defaultView.getComputedStyle(el, "").getPropertyValue(property);
		if (style) {
			return style;
		}
	}
	property = Animator.camelize(property);
	if(el.currentStyle){
		style = el.currentStyle[property];
	}
	return style || el.style[property]
}


CSSStyleSubject.ruleRe = /^\s*([a-zA-Z\-]+)\s*:\s*(\S(.+\S)?)\s*$/;
CSSStyleSubject.numericalRe = /^-?\d+(?:\.\d+)?(%|[a-zA-Z]{2})?$/;
CSSStyleSubject.discreteRe = /^\w+$/;

/*
CSSStyleSubject.cssProperties = ['background-color','border','border-color','border-spacing',
'border-style','border-top','border-right','border-bottom','border-left','border-top-color',
'border-right-color','border-bottom-color','border-left-color','border-top-width','border-right-width',
'border-bottom-width','border-left-width','border-width','bottom','color','font-size','font-size-adjust',
'font-stretch','font-style','height','left','letter-spacing','line-height','margin','margin-top',
'margin-right','margin-bottom','margin-left','marker-offset','max-height','max-width','min-height',
'min-width','orphans','outline','outline-color','outline-style','outline-width','overflow','padding',
'padding-top','padding-right','padding-bottom','padding-left','quotes','right','size','text-indent',
'top','width','word-spacing','z-index','opacity','outline-offset'];*/


CSSStyleSubject.cssProperties = ['azimuth','background','background-attachment','background-color','background-image','background-position','background-repeat','border-collapse','border-color','border-spacing','border-style','border-top','border-top-color','border-right-color','border-bottom-color','border-left-color','border-top-style','border-right-style','border-bottom-style','border-left-style','border-top-width','border-right-width','border-bottom-width','border-left-width','border-width','bottom','clear','clip','color','content','cursor','direction','display','elevation','empty-cells','css-float','font','font-family','font-size','font-size-adjust','font-stretch','font-style','font-variant','font-weight','height','left','letter-spacing','line-height','list-style','list-style-image','list-style-position','list-style-type','margin','margin-top','margin-right','margin-bottom','margin-left','max-height','max-width','min-height','min-width','orphans','outline','outline-color','outline-style','outline-width','overflow','padding','padding-top','padding-right','padding-bottom','padding-left','pause','position','right','size','table-layout','text-align','text-decoration','text-indent','text-shadow','text-transform','top','vertical-align','visibility','white-space','width','word-spacing','z-index','opacity','outline-offset','overflow-x','overflow-y'];


function AnimatorChain(animators, options) {
	this.animators = animators;
	this.setOptions(options);
	for (var i=0; i<this.animators.length; i++) {
		this.listenTo(this.animators[i]);
	}
	this.forwards = false;
	this.current = 0;
}

AnimatorChain.prototype = {
	setOptions: function(options) {
		this.options = Animator.applyDefaults({
			resetOnPlay: true
		}, options);
	},
	play: function() {
		this.forwards = true;
		this.current = -1;
		if (this.options.resetOnPlay) {
			for (var i=0; i<this.animators.length; i++) {
				this.animators[i].jumpTo(0);
			}
		}
		this.advance();
	},
	reverse: function() {
		this.forwards = false;
		this.current = this.animators.length;
		if (this.options.resetOnPlay) {
			for (var i=0; i<this.animators.length; i++) {
				this.animators[i].jumpTo(1);
			}
		}
		this.advance();
	},
	toggle: function() {
		if (this.forwards) {
			this.seekTo(0);
		} else {
			this.seekTo(1);
		}
	},
	listenTo: function(animator) {
		var oldOnComplete = animator.options.onComplete;
		var _this = this;
		animator.options.onComplete = function() {
			if (oldOnComplete) oldOnComplete.call(animator);
			_this.advance();
		}
	},
	advance: function() {
		if (this.forwards) {
			if (this.animators[this.current + 1] == null) return;
			this.current++;
			this.animators[this.current].play();
		} else {
			if (this.animators[this.current - 1] == null) return;
			this.current--;
			this.animators[this.current].reverse();
		}
	},
	seekTo: function(target) {
		if (target <= 0) {
			this.forwards = false;
			this.animators[this.current].seekTo(0);
		} else {
			this.forwards = true;
			this.animators[this.current].seekTo(1);
		}
	}
}

function Accordion(options) {
	this.setOptions(options);
	var selected = this.options.initialSection, current;
	if (this.options.rememberance) {
		current = document.location.hash.substring(1);
	}
	this.rememberanceTexts = [];
	this.ans = [];
	var _this = this;
	for (var i=0; i<this.options.sections.length; i++) {
		var el = this.options.sections[i];
		var an = new Animator(this.options.animatorOptions);
		var from = this.options.from + (this.options.shift * i);
		var to = this.options.to + (this.options.shift * i);
		an.addSubject(new NumericalStyleSubject(el, this.options.property, from, to, this.options.units));
		an.jumpTo(0);
		var activator = this.options.getActivator(el);
		activator.index = i;
		activator.onclick = function(){_this.show(this.index)};
		this.ans[this.ans.length] = an;
		this.rememberanceTexts[i] = activator.innerHTML.replace(/\s/g, "");
		if (this.rememberanceTexts[i] === current) {
			selected = i;
		}
	}
	this.show(selected);
}

Accordion.prototype = {
	setOptions: function(options) {
		this.options = Object.extend({
			sections: null,
			getActivator: function(el) {return document.getElementById(el.getAttribute("activator"))},
			shift: 0,
			initialSection: 0,
			rememberance: true,
			animatorOptions: {}
		}, options || {});
	},
	show: function(section) {
		for (var i=0; i<this.ans.length; i++) {
			this.ans[i].seekTo(i > section ? 1 : 0);
		}
		if (this.options.rememberance) {
			document.location.hash = this.rememberanceTexts[section];
		}
	}
}



Array.prototype.each = function(iterator) {
  try {
    for(var index=0;index<this.length;index++) {
      var item = this[index] ;
      iterator.call(item,item,index) ;
    }
  } catch (e) {
    if (e != $break) throw e ;
  }
  return this ;
} ;

Array.prototype.invoke = function(iterator) {
  var args = $A(arguments) ;
  var methodName = args.shift() ;
  var ret = [] ;
  try {
    for(var index=0;index<this.length;index++) {
      var item = this[index] ;
      ret.push(item[methodName].apply(item,args)) ;
    }
  } catch (e) {
    if (e != $break) throw e ;
  }
  return ret ;
} ;

Array.prototype.map = function(iterator) {
  var ret = [] ;
  try {
    for(var index=0;index<this.length;index++) {
      var item = this[index] ;
      ret.push((iterator || Prototype.K).call(item,item,index)) 
    }
  } catch (e) {
    if (e != $break) throw e ;
  }
  return ret ;
}

Array.prototype.collect = Array.prototype.map ;



require('Core') ;


SC.Benchmark = {

  total: 0, 
  topLevelOnly: true,

  reset: function() { this.total = 0 ; },
  
  install: function(object,method) {
    var __func = object['b__' + method] = object[method] ;
    object[method] = function() {
      var that = this; var args = arguments ;
      if (SC.Benchmark.topLevelOnly && SC.Benchmark._running > 0) {
        return __func.apply(that, args) ;
      } 
      SC.Benchmark._running += 1 ;
      var label = method + "(" + $A(arguments).join(", ") + ")" ;
      var ret = SC.Benchmark._bench(function() { return __func.apply(that,args); }, label) ;
      SC.Benchmark._running -= 1 ;
      return ret ;
    }  
  },
  
  restore: function(object,method) {
    object[method] = object['b__' + method] ;
  },
  
  _counts: {},
  
  _bench: function(func, name) {
    var timeIn = new Date().getTime() ;
    var ret = func() ;
    var timeOut = new Date().getTime() ;
    var dist = timeOut - timeIn ;
    SC.Benchmark.total += dist ;
    
    var count = SC.Benchmark._counts[name] || 0 ;
    SC.Benchmark._counts[name] = count = count + 1 ;
    console.log("BENCH: " + (dist) + " msec:" + name + ' (' + count + 'x)') ;
    return ret ;
  },
  
  _running: 0 
  
}


require('foundation/object') ;


SC.Binding = SC.Object.extend({
  
  from: '', to: '',
  
  oneWay: false,
  
  emptyPlaceholder: null,
  
  nullPlaceholder: null,
  
  multiplePlaceholder: null,
  
  transform: null,
  
  
  connect: function() {
    if (this._connected) return ;
    var funcs = this._boundObservers() ;
    
    SC.Observers.addObserver(this.get('from'),funcs.from) ;
    SC.Observers.addObserver(this.get('to'),funcs.to) ;
    this._connected = true ;
    return this ;
  },
  
  disconnect: function() {  
    if (!this._connected) return ;
    var funcs = this._boundObservers() ;
    SC.Observers.removeObserver(this.get('from'),funcs.from) ;
    SC.Observers.removeObserver(this.get('to'),funcs.to) ;
    this._connected = false ;
    return this ;
  },
  
  relay: function() { 
    var tuple = SC.Object.tupleForPropertyPath(this.get('from')) ;
    if (tuple) tuple = this._walkTuple(tuple) ;
    if (tuple) this._fromObserver(tuple[0],tuple[1],tuple[0].get(tuple[1]));
  },
  

  init: function() {
    arguments.callee.base.call(this) ;
    this.connect() ;
  },
  
  _boundObservers: function() {
    var ret = this._boundObserverFuncs ;
    if (!ret) {
      this._boundObserverFuncs = ret = {
        from: this._fromObserver.bind(this),
        to: this._toObserver.bind(this)
      }
    }
    return ret ;
  },
  
  _fromObserver: function(target,key,value, propertyRevision) {
    if (propertyRevision <= this._lastFromPropertyRevision) return ;
    this._lastFromPropertyRevision = propertyRevision ;
    
    if (!this._didChange(this._lastFromValue,value)) return ;
    
    var tuple = SC.Object.tupleForPropertyPath(this.get('to')) ;
    if (tuple) tuple = this._walkTuple(tuple) ;
    if (tuple) {

      var transformFunc = this.transform ;
      if (transformFunc) value = transformFunc('to', key, value) ;

      var pholder ;

      if (value && (value == []) && (pholder = this.get('emptyPlaceholder'))) {
        value = pholder ;

      } else if (value && (value instanceof Array) && (pholder = this.get('multiplePlaceholder'))) {
        value = (value.length == 1) ? value[0] : pholder ;
      }

      if ((value == null) && (pholder = this.get('nullPlaceholder') || this.get('emptyPlaceholder'))) {
        value = pholder ;
      }

      tuple[0].set(tuple[1],value) ;      
      this._lastToPropertyRevision = tuple[0].propertyRevision ;
      
    }
  },

  _toObserver: function(target,key,value, propertyRevision) {
    if (this.get('oneWay')) return ; // block.

    if (propertyRevision <= this._lastToPropertyRevision) return ;
    this._lastToPropertyRevision = propertyRevision ;
    
    if (!this._didChange(this._lastToValue,value)) return ;
    
    var tuple = SC.Object.tupleForPropertyPath(this.get('from')) ;
    if (tuple) tuple = this._walkTuple(tuple) ;
    if (tuple) {
      var transformFunc = this.get('transform') ;
      if (transformFunc) value = transformFunc('from', key, value) ;
      
      var result = tuple[0].set(tuple[1],value) ;
      if (result) this._lastFromPropertyRevision = result.propertyRevision ;
      
      if (result != value) {
        target.set(key,result) ;
        this._lastToPropertyRevision = target.propertyRevision ;
      }
      
    } 
  },
  
  _didChange: function(lastValue, newValue) {
    if (newValue && lastValue) {
      if (typeof(newValue) == typeof(lastValue)) {
        if (lastValue == newValue) return false ;
      }
    } else if (((newValue === null) && (lastValue === null)) || ((newValue === undefined) && (lastValue === undefined))) return false ;
    return true ;
  },
  
  _lastToPropertyRevision: 0,
  _lastFromPropertyRevision: 0,
  
  _walkTuple: function(tuple) {
    var parts = tuple[1].split('.') ;
    if (parts.length > 1) {
      tuple = tuple.slice() ; // duplicate to avoid an error.
      var obj = tuple[0] ;
      tuple[1] = parts.pop() ;
      for(var loc=0;(obj && (loc<parts.length));loc++) {
        obj = obj.get(parts[loc]) ;
      }
      tuple[0] = obj ;
    }
    return (tuple[0] && tuple[1]) ? tuple : null ;
  }
  
}) ;

SC.Binding.mixin({
  MULTIPLE_PLACEHOLDER: '@@MULT@@',
  NULL_PLACEHOLDER: '@@NULL@@',
  EMPTY_PLACEHOLDER: '@@EMPTY@@'
}) ;

SC.Binding.From = function(from,opts) {
  if (!opts) opts = {} ;
  if ((typeof(from) == "string") || (from instanceof Array)) {
    opts.from = from ;
  } else Object.extend(opts,from) ;
  var ret = SC.Binding.extend(opts) ; 
  return ret ;
} ;

SC.Binding.build = function(tr) {
  return function(from) { 
    return SC.Binding.From(from,{ transform: tr }) ; 
  }
} ; 

SC.Binding.NoChange = SC.Binding.From;

SC.Binding.NoError = SC.Binding.build(function(dir, key, value) {
  return ($type(value) == T_ERROR) ? null : value ;  
}) ;

SC.Binding.NoError.ext = function(bindFunc) {
  return function(d,k,v) {
    return ($type(value) == T_ERROR) ? null : bindFunc(d,k,v) ;  
  }
} ;

SC.Binding.Single = SC.Binding.build(function(d,k,v) {
  if ($type(v) == T_ARRAY) {
    switch(v) {
      case 0:
        v = null ;
        break ;
      case 1:
        v = v[0] ;
        break ;
      default:
        v = SC.Binding.MULTIPLE_PLACEHOLDER ;
    }
  }
  return v;
});

SC.Binding.SingleNull = SC.Binding.build(function(d,k,v) {
  if ($type(v) == T_ARRAY) {
    switch(v) {
      case 0:
        v = null ;
        break ;
      case 1:
        v = v[0] ;
        break ;
      default:
        v = null ;
    }
  }
  return v;
});

SC.Binding.SingleNoError = SC.Binding.NoError.ext(SC.Binding.Single);
SC.Binding.SingleNullNoError = SC.Binding.NoError.ext(SC.Binding.SingleNull);

SC.Binding.Multiple = SC.Binding.build(function(d,k,v) {
  var t = $type(v) ;
  if (t != T_ARRAY) {
    if (t == null) {
      v = [] ;
    } else if (t != T_ERROR) {
      v = [v] ;
    }
  }
  return v ;
}) ;

SC.Binding.MultipleNoError = SC.Binding.NoError.ext(SC.Binding.Multiple);

SC.Binding.Bool = SC.Binding.build(function(d,k,v) {
  return ($type(v) == T_ARRAY) ? (t.length > 0) : !!v ;
}) ;

SC.Binding.NotNull = SC.Binding.build(function(d,k,v) { 
  return (v != null) ;
}) ;

SC.Binding.Not = SC.Binding.build(function(d,k,v) {
  return !(($type(v) == T_ARRAY) ? (t.length > 0) : !!v) ;
}) ;

SC.Binding.IsNull = SC.Binding.build(function(d,k,v) {
  return (v == null) ;
}) ;

SC.Binding.BoolNoError = SC.Binding.NoError.ext(SC.Binding.Bool) ;
SC.Binding.NotNullNoError = SC.Binding.NoError.ext(SC.Binding.NotNull) ;
SC.Binding.NotNoError = SC.Binding.NoError.ext(SC.Binding.Not) ;
SC.Binding.IsNullNoError = SC.Binding.NoError.ext(SC.Binding.IsNull) ;


SC.Binding.Multiple = function(from) {
  return SC.Binding.From(from,{
    transform: function(dir,key,value) { 
      return (value) ? ((value instanceof Array) ? value : [value]) : value;
    }
  }) ;
}

SC.Binding.MultipleNotEmpty = function(from) {
  return SC.Binding.From(from,{
    transform: function(dir,key,value) { 
      return (value) ? ((value instanceof Array) ? value : [value]) : [];
    }
  }) ;
}

SC.Binding.SingleNotEmpty = function(from) {
  return SC.Binding.From(from,{
    multiplePlaceholder: SC.Binding.MULTIPLE_PLACEHOLDER,
    emptyPlaceholder: SC.Binding.EMPTY_PLACEHOLDER,
    nullPlaceholder: SC.Binding.NULL_PLACEHOLDER
  }) ;
}

SC.Binding.OneWay = function(from) {
  return SC.Binding.From(from, { oneWay: true }) ;
}

SC.Binding.Flag = function(from) {
  return SC.Binding.From(from, { 
    transform: function(dir,key,value) {
      return (value && (value instanceof Array)) ? (value.length == 0) : !!value ;
    }
  }) ;
}

SC.Binding.OneWayFlag = function(from) {
  var ret = SC.Binding.Flag(from) ;
  ret.oneWay = true ;
  return ret ;  
}





var MONTH_NAMES=new Array('January','February','March','April','May','June','July','August','September','October','November','December','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
var DAY_NAMES=new Array('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sun','Mon','Tue','Wed','Thu','Fri','Sat');
function LZ(x) {return(x<0||x>9?"":"0")+x}

Object.extend(Date,{
  
  isDate: function(val,format) {
  	var date = Date.getDateFromFormat(val,format);
  	if (date==0) { return false; }
  	return true;
	},

  compareDates: function(date1,dateformat1,date2,dateformat2) {
  	var d1= Date.getDateFromFormat(date1,dateformat1);
  	var d2= Date.getDateFromFormat(date2,dateformat2);
  	if (d1==0 || d2==0) {
  		return -1;
  		}
  	else if (d1 > d2) {
  		return 1;
  		}
  	return 0;
	},
	
  getDateFromFormat: function(val,format) {
  	val=val+"";
  	format=format+"";
  	var i_val=0;
  	var i_format=0;
  	var c="";
  	var token="";
  	var token2="";
  	var x,y;
  	var now=new Date();
  	var year=now.getYear();
  	var month=now.getMonth()+1;
  	var date=1;
  	var hh=now.getHours();
  	var mm=now.getMinutes();
  	var ss=now.getSeconds();
  	var ampm="";

  	while (i_format < format.length) {
  		c=format.charAt(i_format);
  		token="";
  		while ((format.charAt(i_format)==c) && (i_format < format.length)) {
  			token += format.charAt(i_format++);
  			}
  		if (token=="yyyy" || token=="yy" || token=="y") {
  			if (token=="yyyy") { x=4;y=4; }
  			if (token=="yy")   { x=2;y=2; }
  			if (token=="y")    { x=2;y=4; }
  			year=Date._getInt(val,i_val,x,y);
  			if (year==null) { return 0; }
  			i_val += year.length;
  			if (year.length==2) {
  				if (year > 70) { year=1900+(year-0); }
  				else { year=2000+(year-0); }
  				}
  			}
  		else if (token=="MMM"||token=="NNN"){
  			month=0;
  			for (var i=0; i<MONTH_NAMES.length; i++) {
  				var month_name=MONTH_NAMES[i];
  				if (val.substring(i_val,i_val+month_name.length).toLowerCase()==month_name.toLowerCase()) {
  					if (token=="MMM"||(token=="NNN"&&i>11)) {
  						month=i+1;
  						if (month>12) { month -= 12; }
  						i_val += month_name.length;
  						break;
  						}
  					}
  				}
  			if ((month < 1)||(month>12)){return 0;}
  			}
  		else if (token=="EE"||token=="E"){
  			for (var i=0; i<DAY_NAMES.length; i++) {
  				var day_name=DAY_NAMES[i];
  				if (val.substring(i_val,i_val+day_name.length).toLowerCase()==day_name.toLowerCase()) {
  					i_val += day_name.length;
  					break;
  					}
  				}
  			}
  		else if (token=="MM"||token=="M") {
  			month=Date._getInt(val,i_val,token.length,2);
  			if(month==null||(month<1)||(month>12)){return 0;}
  			i_val+=month.length;}
  		else if (token=="dd"||token=="d") {
  			date=Date._getInt(val,i_val,token.length,2);
  			if(date==null||(date<1)||(date>31)){return 0;}
  			i_val+=date.length;}
  		else if (token=="hh"||token=="h") {
  			hh=Date._getInt(val,i_val,token.length,2);
  			if(hh==null||(hh<1)||(hh>12)){return 0;}
  			i_val+=hh.length;}
  		else if (token=="HH"||token=="H") {
  			hh=Date._getInt(val,i_val,token.length,2);
  			if(hh==null||(hh<0)||(hh>23)){return 0;}
  			i_val+=hh.length;}
  		else if (token=="KK"||token=="K") {
  			hh=Date._getInt(val,i_val,token.length,2);
  			if(hh==null||(hh<0)||(hh>11)){return 0;}
  			i_val+=hh.length;}
  		else if (token=="kk"||token=="k") {
  			hh=Date._getInt(val,i_val,token.length,2);
  			if(hh==null||(hh<1)||(hh>24)){return 0;}
  			i_val+=hh.length;hh--;}
  		else if (token=="mm"||token=="m") {
  			mm=Date._getInt(val,i_val,token.length,2);
  			if(mm==null||(mm<0)||(mm>59)){return 0;}
  			i_val+=mm.length;}
  		else if (token=="ss"||token=="s") {
  			ss=Date._getInt(val,i_val,token.length,2);
  			if(ss==null||(ss<0)||(ss>59)){return 0;}
  			i_val+=ss.length;}
  		else if (token=="a") {
  			if (val.substring(i_val,i_val+2).toLowerCase()=="am") {ampm="AM";}
  			else if (val.substring(i_val,i_val+2).toLowerCase()=="pm") {ampm="PM";}
  			else {return 0;}
  			i_val+=2;}
  		else {
  			if (val.substring(i_val,i_val+token.length)!=token) {return 0;}
  			else {i_val+=token.length;}
  			}
  		}
  	if (i_val != val.length) { return 0; }
  	if (month==2) {
  		if ( ( (year%4==0)&&(year%100 != 0) ) || (year%400==0) ) { // leap year
  			if (date > 29){ return 0; }
  			}
  		else { if (date > 28) { return 0; } }
  		}
  	if ((month==4)||(month==6)||(month==9)||(month==11)) {
  		if (date > 30) { return 0; }
  		}
  	if (hh<12 && ampm=="PM") { hh=hh-0+12; }
  	else if (hh>11 && ampm=="AM") { hh-=12; }
  	var newdate=new Date(year,month-1,date,hh,mm,ss);
  	return newdate.getTime();
  },

  parseDate: function(val) {
  	var preferEuro=(arguments.length==2)?arguments[1]:false;
  	generalFormats=new Array('E NNN dd HH:mm:ss UTC yyyy','y-M-d','y-M-d','MMM d, y','MMM d,y','y-MMM-d','d-MMM-y','MMM d','d MMM y','d.MMM.y','y MMM d','y.MMM.d');
  	monthFirst=new Array('M/d/y','M-d-y','M.d.y','MMM-d','M/d','M-d');
  	dateFirst =new Array('d/M/y','d-M-y','d.M.y','d-MMM','d/M','d-M');
  	var checkList=new Array('generalFormats',preferEuro?'dateFirst':'monthFirst',preferEuro?'monthFirst':'dateFirst');
  	var d=null;
  	
  	d = 0 ; var now = new Date().getTime() ;
  	switch(val.toLowerCase()) {
  	  case 'yesterday'.loc():
  	    d = now - (24*60*60*1000) ;
  	    break ;
  	  case 'today'.loc():
  	  case 'now'.loc():
  	    d = now ;
  	    break ;
  	  case 'tomorrow'.loc():
  	    d = now + (24*60*60*1000) ;
  	    break;
  	}
  	if (d>0) return new Date(d) ;
  	
  	for (var i=0; i<checkList.length; i++) {
  		var l=window[checkList[i]];
  		for (var j=0; j<l.length; j++) {
  			d=Date.getDateFromFormat(val,l[j]);
  			if (d==0) d = Date.getDateFromFormat(val,l[j] + ' H:m:s') ;
  			if (d==0) d = Date.getDateFromFormat(val,l[j] + ' h:m:s a') ;
  			if (d!=0) return new Date(d); 
  		}
  	}
  	return null;
  },
  
  _isInteger: function(val) {
  	var digits="1234567890";
  	for (var i=0; i < val.length; i++) {
  		if (digits.indexOf(val.charAt(i))==-1) { return false; }
  	}
  	return true;
  },
  
  _getInt: function(str,i,minlength,maxlength) {
  	for (var x=maxlength; x>=minlength; x--) {
  		var token=str.substring(i,i+x);
  		if (token.length < minlength) { return null; }
  		if (Date._isInteger(token)) { return token; }
  	}
  	return null;
  }

}) ;

Object.extend(Date.prototype, {
  
  format: function(format) {
  	format=format+"";
    var date = this ;
  	var result="";
  	var i_format=0;
  	var c="";
  	var token="";
  	var y=date.getYear()+"";
  	var M=date.getMonth()+1;
  	var d=date.getDate();
  	var E=date.getDay();
  	var H=date.getHours();
  	var m=date.getMinutes();
  	var s=date.getSeconds();
  	var yyyy,yy,MMM,MM,dd,hh,h,mm,ss,ampm,HH,H,KK,K,kk,k;
  	var value=new Object();
  	if (y.length < 4) {y=""+(y-0+1900);}
  	value["y"]=""+y;
  	value["yyyy"]=y;
  	value["yy"]=y.substring(2,4);
  	value["M"]=M;
  	value["MM"]=LZ(M);
  	value["MMM"]=MONTH_NAMES[M-1];
  	value["NNN"]=MONTH_NAMES[M+11];
  	value["d"]=d;
  	value["dd"]=LZ(d);
  	value["E"]=DAY_NAMES[E+7];
  	value["EE"]=DAY_NAMES[E];
  	value["H"]=H;
  	value["HH"]=LZ(H);
  	if (H==0){value["h"]=12;}
  	else if (H>12){value["h"]=H-12;}
  	else {value["h"]=H;}
  	value["hh"]=LZ(value["h"]);
  	if (H>11){value["K"]=H-12;} else {value["K"]=H;}
  	value["k"]=H+1;
  	value["KK"]=LZ(value["K"]);
  	value["kk"]=LZ(value["k"]);
  	if (H > 11) { value["a"]="PM"; }
  	else { value["a"]="AM"; }
  	value["m"]=m;
  	value["mm"]=LZ(m);
  	value["s"]=s;
  	value["ss"]=LZ(s);
  	while (i_format < format.length) {
  		c=format.charAt(i_format);
  		token="";
  		while ((format.charAt(i_format)==c) && (i_format < format.length)) {
  			token += format.charAt(i_format++);
  			}
  		if (value[token] != null) { result=result + value[token]; }
  		else { result=result + token; }
  		}
  	return result;
  },
  
  utcFormat: function() { return (new Date(this.getTime() + (this.getTimezoneOffset() * 60 * 1000))).format('E NNN dd HH:mm:ss UTC yyyy'); }

}) ;


require('foundation/object');

SC.Error = SC.Object.extend({
  code: -1, // error code. Used to designate type.
  description: '' // human readable dscriptions.
}) ;

SC.Error.desc = function(description, code) {
  var opts = { description: description } ;
  if (code !== undefined) opts.code = code ;
  return this.create(opts) ;
}

function $error(description, c) { return SC.Error.desc(description,c); }



require('Core') ;
require('foundation/object') ;

SC.InputManager = SC.Object.extend({
  
  interpretKeyEvents: function(event, responder) {
    var codes = this.codesForEvent(event) ;
    var cmd = codes[0]; var chr = codes[1];
    if (!cmd && !chr) return false ; //nothing to do.
    
    if (cmd) {
      var methodName ;
      methodName = SC.MODIFIED_COMMAND_MAP[cmd] ;
      if (!methodName) {
        methodName = SC.BASE_COMMAND_MAP[cmd.split('_').last()];
      }
      
      if (methodName) {
        return responder.doCommand(methodName, null, cmd, event) ;
      } else {
        if (responder.doCommand('performKeyEquivalent', cmd, event)) {
          return true;
        }
      }
    }
    
    if (chr) return responder.doCommand('insertText', chr) ;
    return false ;
  },
  
  codesForEvent: function(e) {
    var code = e.keyCode ;
    var ret = null ; var key = null ;
    var modifiers = '' ;
    
    if (code) {
      ret = SC.FUNCTION_KEYS[code] ;
      if (!ret && (e.altKey || e.ctrlKey)) ret = SC.PRINTABLE_KEYS[code] ;
      if (ret) {
        if (e.altKey) modifiers += 'alt_' ;
        if (e.ctrlKey) modifiers += 'ctrl_' ;
        if (e.shiftKey) modifiers += 'shift_' ;
      }
    }

    if (!ret) {
      var code = e.charCode || e.keyCode ;
      key = ret = String.fromCharCode(code) ;
      var lowercase = ret.toLowerCase() ;
      if (ret != lowercase) {
        modifiers = 'shift_' ;
        ret = lowercase ;
      } else ret = null ;
    }

    if (ret) ret = modifiers + ret ;
    return [ret, key] ;
  }
    
});

SC.MODIFIED_COMMAND_MAP = {
  'ctrl_.': 'cancel',
  'shift_tab': 'insertBacktab',
  'shift_left': 'moveLeftAndModifySelection',
  'shift_right': 'moveRightAndModifySelection',
  'shift_up': 'moveUpAndModifySelection',
  'shift_down': 'moveDownAndModifySelection',
  'alt_left': 'moveLeftAndModifySelection',
  'alt_right': 'moveRightAndModifySelection',
  'alt_up': 'moveUpAndModifySelection',
  'alt_down': 'moveDownAndModifySelection',
  'ctrl_a': 'selectAll'
} ;

SC.BASE_COMMAND_MAP = {
  'escape': 'cancel',
  'backspace': 'deleteBackward',
  'delete': 'deleteForward',
  'return': 'insertNewline',
  'tab': 'insertTab',
  'left': 'moveLeft',
  'right': 'moveRight',
  'up': 'moveUp',
  'down': 'moveDown',
  'home': 'moveToBeginningOfDocument',
  'end': 'moveToEndOfDocument',
  'pagedown': 'pageDown',
  'pageup': 'pageUp'
} ;

SC.FUNCTION_KEYS = {
  8: 'backspace',  9: 'tab',  13: 'return',  19: 'pause',  27: 'escape',  
  32: 'space',  33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 
  37: 'left', 38: 'up', 39: 'right', 40: 'down', 44: 'printscreen', 
  45: 'insert', 46: 'delete', 112: 'f1', 113: 'f2', 114: 'f3', 115: 'f4', 
  116: 'f5', 117: 'f7', 119: 'f8', 120: 'f9', 121: 'f10', 122: 'f11', 
  123: 'f12', 144: 'numlock', 145: 'scrolllock'
} ;

SC.PRINTABLE_KEYS = {
  48:"0", 49:"1", 50:"2", 51:"3", 52:"4", 53:"5", 54:"6", 55:"7", 56:"8",
  57:"9", 59:";", 61:"=", 65:"a", 66:"b", 67:"c", 68:"d", 69:"e", 70:"f",
  71:"g", 72:"h", 73:"i", 74:"j", 75:"k", 76:"l", 77:"m", 78:"n", 79:"o",
  80:"p", 81:"q", 82:"r", 83:"s", 84:"t", 85:"u", 86:"v", 87:"w", 88:"x",
  89:"y", 90:"z", 107:"+", 109:"-", 110:".", 188:",", 190:".", 191:"/",
  192:"`", 219:"[", 220:"\\", 221:"]", 222:"\""
} ;

/*
    json.js
    2007-03-20

    Public Domain

    This file adds these methods to JavaScript:

        array.toJSONString()
        boolean.toJSONString()
        date.toJSONString()
        number.toJSONString()
        object.toJSONString()
        string.toJSONString()
            These methods produce a JSON text from a JavaScript value.
            It must not contain any cyclical references. Illegal values
            will be excluded.

            The default conversion for dates is to an ISO string. You can
            add a toJSONString method to any date object to get a different
            representation.

        string.parseJSON(filter)
            This method parses a JSON text to produce an object or
            array. It can throw a SyntaxError exception.

            The optional filter parameter is a function which can filter and
            transform the results. It receives each of the keys and values, and
            its return value is used instead of the original value. If it
            returns what it received, then structure is not modified. If it
            returns undefined then the member is deleted.

            Example:


            myData = text.parseJSON(function (key, value) {
                return key.indexOf('date') >= 0 ? new Date(value) : value;
            });

    It is expected that these methods will formally become part of the
    JavaScript Programming Language in the Fourth Edition of the
    ECMAScript standard in 2008.

    This file will break programs with improper for..in loops. See
    http://yuiblog.com/blog/2006/09/26/for-in-intrigue/

    This is a reference implementation. You are free to copy, modify, or
    redistribute.

    Use your own copy. It is extremely unwise to load untrusted third party
    code into your pages.
*/


if (!Object.toJSONString) {

    Array.prototype.toJSONString = function () {
        var a = ['['],  // The array holding the text fragments.
            b,          // A boolean indicating that a comma is required.
            i,          // Loop counter.
            l = this.length,
            v;          // The value to be stringified.

        function p(s) {


            if (b) {
                a.push(',');
            }
            a.push(s);
            b = true;
        }


        for (i = 0; i < l; i += 1) {
            v = this[i];
            switch (typeof v) {


            case 'object':
                if (v) {
                    if (typeof v.toJSONString === 'function') {
                        p(v.toJSONString());
                    } else p(Object.toJSONString(v)) ;
                } else {
                    p("null");
                }
                break;


            case 'string':
            case 'number':
            case 'boolean':
                p(v.toJSONString());


            }
        }


        a.push(']');
        return a.join('');
    };


    Boolean.prototype.toJSONString = function () {
        return String(this);
    };


    Date.prototype.toJSONString = function () {


        function f(n) {


            return n < 10 ? '0' + n : n;
        }

        return '"' + this.getFullYear() + '-' +
                f(this.getMonth() + 1) + '-' +
                f(this.getDate()) + 'T' +
                f(this.getHours()) + ':' +
                f(this.getMinutes()) + ':' +
                f(this.getSeconds()) + '"';
    };


    Number.prototype.toJSONString = function () {


        return isFinite(this) ? String(this) : "null";
    };


    Object.toJSONString = function (object) {
        var a = ['{'],  // The array holding the text fragments.
            b,          // A boolean indicating that a comma is required.
            k,          // The current key.
            v;          // The current value.

        function p(s) {


            if (b) {
                a.push(',');
            }
            a.push(k.toJSONString(), ':', s);
            b = true;
        }


        for (k in object) {
            if (object.hasOwnProperty(k)) {
                v = object[k];
                switch (typeof v) {


                case 'object':
                    if (v) {
                        if (typeof v.toJSONString === 'function') {
                            p(v.toJSONString());
                        } else p(Object.toJSONString(v)) ;
                    } else {
                        p("null");
                    }
                    break;

            case 'string':
            case 'number':
            case 'boolean':
                    p(v.toJSONString());


                }
            }
        }


        a.push('}');
        return a.join('');
    };


    (function (s) {



        var m = {
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        };


        s.parseJSON = function (filter) {


            try {
                if (/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.
                        test(this)) {


                    var j = eval('(' + this + ')');


                    if (typeof filter === 'function') {

                        function walk(k, v) {
                            if (v && typeof v === 'object') {
                                for (var i in v) {
                                    if (v.hasOwnProperty(i)) {
                                        v[i] = walk(i, v[i]);
                                    }
                                }
                            }
                            return filter(k, v);
                        }

                        j = walk('', j);
                    }
                    return j;
                }
            } catch (e) {


            }
            throw new SyntaxError("parseJSON");
        };


        s.toJSONString = function () {


            if (/["\\\x00-\x1f]/.test(this)) {
                return '"' + this.replace(/([\x00-\x1f\\"])/g, function(a, b) {
                    var c = m[b];
                    if (c) {
                        return c;
                    }
                    c = b.charCodeAt();
                    return '\\u00' +
                        Math.floor(c / 16).toString(16) +
                        (c % 16).toString(16);
                }) + '"';
            }
            return '"' + this + '"';
        };
    })(String.prototype);
}


require('Core') ;

SC.NodeDescriptor = {
  create: function(descriptor, opts) {
    if (!opts) opts = {} ;
    var tag = opts.tag || descriptor.tag || 'div' ;
    var className = opts.cssClass || descriptor.cssClass ;
    var elementId = opts.id || descriptor.id ;
    var style = opts.style || descriptor.style ;
    var innerHTML = opts.innerHTML || descriptor.innerHTML ;
    if (!innerHTML) {
      var childNodes = opts.childNodes || descriptor.childNodes ;
    } 

    var ret = $(document.createElement(tag)) ;
    if (className) ret.className = className ;
    if (elementId) ret.id = elementId ;
    if (style) {
      for (var name in style) element.style[name.camelize()] = style[name];
    }
    
    for(var attr in descriptor) {
      if (this.ignoredProperties.indexOf(attr) == -1) {
        ret.setAttribute(attr,descriptor[attr]) ;
      }
    }
    
    if (innerHTML) {
      ret.innerHTML = innerHTML ;
    } else if (childNodes) {
      var that = this ;
      childNodes.each(function(desc) {
        ret.appendChild(that.create(desc)) ;
      }) ;
    }
    
    return ret ;
  },
  
  ignoredProperties: ['tag','cssClass','id','style','childNodes','innerHTML']
}



require('Core') ;
require('foundation/benchmark') ;

BENCHMARK_SELECTOR = NO ;
NO_LIMIT = 10000 ;

SC.PathModule = {

    $$func: function(func, levels, max, nest) {
      return SC._PathModule.$$func(this.rootElement,func,levels,max,nest);
    },

    $$C: function(className, levels, max, nest) {
      return SC._PathModule.$$C(this.rootElement,className,levels,max,nest);
    },

    $$T: function(tagName, levels, max, nest) {
      return SC._PathModule.$$T(this.rootElement,tagName,levels,max,nest);
    },

    $$P: function(property, value, levels, max, nest) {
      return SC._PathModule.$$P(this.rootElement, property, value, levels, max, nest);
    },

    $$S: function(selector, levels, max, nest) {
      return SC._PathModule.$$S(this.rootElement, selector, levels, max, nest);
    },

    $func: function(func, levels) {
      return SC._PathModule.$func(this.rootElement, func, levels);
    },

    $C: function(className, levels) {
      return SC._PathModule.$C(this.rootElement, className, levels);
    },

    $T: function(tagName, levels) {
      return SC._PathModule.$T(this.rootElement, tagName, levels);
    },

    $P: function(attr, value, levels) {
      return SC._PathModule.$P(this.rootElement, attr, value, levels);
    },

    $S: function(selector, levels) {
      return SC._PathModule.$S(this.rootElement, selector, levels);
    },
    
    $$view: function(selector, viewClass, levels, max, nest) {
      return SC._PathModule.$$view(this.rootElement, selector, viewClass, levels, max, nest) ;
    },

    $view: function(selector, viewClass, levels) {
      return SC._PathModule.$view(this.rootElement, selector, levels) ;
    }
    
};

SC._PathModule = {

  $$func: function(el, func, levels, max, nest) {
    levels = levels || NO_LIMIT ; max = max || NO_LIMIT; nest = nest || false;
    
    var searchNode = function(node, depth, remain, includeThisNode) {
      var ret = [] ;
      
      var match = (includeThisNode) ? func(node) : false ;
      if (match) { ret.push(node); remain--; }

      depth-- ;
      if ((match && !nest) || (remain<=0) || (depth<=0)) return ret;

      node = node.firstChild ;
      while(node && (remain > 0)) {
        var found = searchNode(node,depth,remain,true) ;
        remain -= found.length ;
        ret = ret.concat(found) ;
        node = node.nextSibling ;
      }
      return ret ;
    } ;
    
    return searchNode(el || document, levels+1, max, false) ;
  },
  
  $$C: function(el, className, levels, max, nest) {
    return SC._PathModule.$$func(el, function(node) {
      return Element.hasClassName(node, className) ; 
    }, levels, max, nest) ;
  },
  
  $$T: function(el, tagName, levels, max, nest) {
    tagName = tagName.toUpperCase() ;
    return SC._PathModule.$$func(el, function(node) { 
      return node.tagName == tagName; 
    }, levels, max, nest) ;
  },

  $$P: function(el, property, value, levels, max, nest) {
    return SC._PathModule.$$func(el, function(node) { 
      var pvalue = (node.getAttribute) ? node.getAttribute(property) : node[property] ;
      return pvalue == value; 
    }, levels, max, nest) ;
  },

  $$S: function(el,selector, levels, max, nest) {
    var parts = selector.split(' ') ;
    var ret = [el] ; var nextMax = null ; var bits ;
    var blevels; var bmax; var q; var indicies ;

    parts.each(function(part) {
      if (part == '?') { nextMax = 1; return ; }

      blevels = levels; bmax = (nextMax) ? nextMax : max ;
      nextMax = null ;

      if (part.slice(part.length-1,part.length) == ']') {
        bits = part.split('[') ;
        part = bits.shift();
        indicies = bits.map(function(b) {return parseInt(b.slice(0,-1),0);});
      } else indicies = null ;

      bits = part.split('?') ;
      part = bits[0] ;
      if (bits.length > 1) {
        bmax = (bits[1].length == 0) ? 1 : parseInt(bits[1],0) ;
      }

      bits = part.split(':') ;
      part = bits[0] ;
      if (bits.length > 1) blevels = (bits[1] == '*') ? '*' : parseInt(bits[1],0) ;
      if (bits.length > 2) bmax = (bits[2] == '*') ? '*' : parseInt(bits[2],0) ;

      if (blevels == '*') blevels = NO_LIMIT ;
      if (bmax == '*') bmac = NO_LIMIT ;

      if (part && part.length > 0) {
        q = part.slice(0,1) ; 
        if (['.','#'].indexOf(q) >= 0) { 
          part = part.slice(1,part.length) ; 
        }
        
        bits = part.split('.') ;
        part = bits[0] ;
        bits = bits.slice(1,bits.length) ;

        var fret = null ;
        if (q == '#') {
          fret = $(part) ;
          fret = (fret) ? [fret] : null ;
          if (fret) ret = fret ;
        }
        
        if (fret == null) {
          fret = [] ;
          for(var i=0;i<ret.length;i++) {
            var found = SC._PathModule.$$func(ret[i],function(node) {
              var match ;
              switch(q) {
                case '.':
                  NODE = node ;
                  var elementClassName = node.className;
                  if (elementClassName && (elementClassName.length > 0)) {
                    match = (elementClassName == part ||
                      elementClassName.match( new RegExp("(^|\\s)" + part + 
                        "(\\s|$)"))) ;
                  } else match = false ;
                  break ;
                case '#':
                  match = node.id == part ;
                  break ;
                default:
                  if (node.tagName) {
                    match = (node.tagName.toLowerCase() == part.toLowerCase()) ;
                  } else { match = false; }
              }

              for(var i=0; match && i < bits.length; i++) {
                if (!Element.hasClassName(node,bits[i])) match = false ; 
              }
              return match ; 
            }, blevels, bmax, nest) ;
            fret = fret.concat(found) ;
          }
          ret = fret ;
        }
      } else if ($type(ret) != T_ARRAY) ret = [ret] ;

      if (indicies && indicies.length > 0 && ret) {
        ret = ret.map(function(el){
          for(var iloc=0; el && (iloc < indicies.length); iloc++) {
            el = el.childNodes[indicies[iloc]] ;
          }
          return el;
        }) ;
      }
    }) ;
    return ret ;
  },

  $func: function(el,func, levels) {
    var ret = SC._PathModule.$$func(el,func,levels,1,false) ;
    return (ret.length>0) ? ret[0] : null ;
  },

  $C: function(el, className, levels) {
    var ret = SC._PathModule.$$C(el, className,levels,1,false) ;
    return (ret.length>0) ? ret[0] : null ;
  },

  $T: function(el, tagName, levels) {
    var ret = SC._PathModule.$$T(el, tagName,levels,1,false) ;
    return (ret.length>0) ? ret[0] : null ;
  },

  $P: function(el, attr, value, levels) {
    var ret = SC._PathModule.$$A(el, attr, value,levels,1,false) ;
    return (ret.length>0) ? ret[0] : null ;
  },

  $S: function(el, selector, levels) {
    var ret = SC._PathModule.$$S(el, selector,levels,1,false) ;
    return (ret.length>0) ? ret[0] : null ;
  },
  
  $$view: function(el, selector, viewClass, levels, max, nest) {
    if (!viewClass) viewClass = SC.View;
    var ret ;
    if (selector && (typeof(selector) != 'string')) {
      ret = [selector] ;
    } else {
      ret = SC._PathModule.$$S(el,selector,levels,max,nest) ;
    }
    if (ret) ret = ret.map(function(x) { return viewClass.viewFor(x); }) ;
    return ret ;
  },

  $view: function(el, selector, viewClass, levels) {
    var ret = SC._PathModule.$$view(el, selector, viewClass, levels, 1, false) ;
    return (ret.length>0) ? ret[0] : null ;
  }
  
} ;

if (BENCHMARK_SELECTOR) {
  ['$$S','$$C','$$T','$S','$C','$T'].each(function(mname) {
    SC.Benchmark.install(SC._PathModule,mname) ;
  })  ;
  SC.Benchmark.install(window,'$$$') ;
}

Object.extend(SC._PathModule, {
  $$class: SC._PathModule.$$C,
  $$tag: SC._PathModule.$$T,
  $$sel: SC._PathModule.$$S,
  $$prop: SC._PathModule.$$P,
  
  $class: SC._PathModule.$C,
  $tag: SC._PathModule.$T,
  $sel: SC._PathModule.$S,
  $prop: SC._PathModule.$P
}) ;

Object.extend(SC.PathModule, {
  $$class: SC.PathModule.$$C,
  $$tag: SC.PathModule.$$T,
  $$sel: SC.PathModule.$$S,
  $$prop: SC.PathModule.$$P,
  
  $class: SC.PathModule.$C,
  $tag: SC.PathModule.$T,
  $sel: SC.PathModule.$S,
  $prop: SC.PathModule.$P
}) ;

Object.extend(Element.Methods,SC._PathModule) ;
Object.extend(Element,SC._PathModule) ;

if (typeof HTMLElement != 'undefined') {
  Object.extend(HTMLElement.prototype,SC.PathModule) ;
}

Object.extend(document,SC.PathModule) ;

Object.extend(Object.extend(window,SC.PathModule), {
  $$func: function(func, levels, max, nest) {
    return document.$$func(func,levels,max,nest) ;
  }
});

Object.extend(Object.extend(Array.prototype,SC.PathModule), {
  $$func: function(func, levels, max, nest) {
    var ret = [] ;
    for(var loc=0;loc<this.length;loc++) {
      ret = ret.concat(this[loc].$$func(func,levels,max,nest)) ;
    }
    return ret ;
  }
}) ;


require('foundation/path_module') ;

SC.Page = SC.Object.extend({
  
  get: function(key) {
    var value = this[key] ;
    if (value && (value instanceof Function) && (value.isOutlet)) {
      var ret = this.outlet(key) ;
      if (SC.window && !ret.parentNode) {
        SC.window._insertBefore(ret, null, false) ;
        SC.window._rebuildChildNodes();
      }
      ret.awake() ;
      return ret ;
    } else return arguments.callee.base.apply(this,arguments) ;
  },
  
  awake: function() {
    arguments.callee.base.call(this) ;
    for(var key in this) {
      if (this.hasOwnProperty(key) && this[key] && this[key].isOutlet) {
        this.get(key) ; // convert to outlet.
      } 
    }
  },
  
  init: function() {
    arguments.callee.base.apply(this,arguments) ;
    var el = this.rootElement = $('resources') ;
    SC.callOnLoad(function() {
      if (el && el.parentNode) el.parentNode.removeChild(el) ;
    }) ;
  },
  
  getIfConfigured: function(key) {
    var value = this[key] ;
    if (value && (value instanceof Function) && (value.isOutlet)) {
      return null ;
    } else return value ;
  }
  
}) ;

Object.extend(SC.Page.prototype, SC.PathModule) ;

require('Core') ;
require('foundation/object') ;
require('foundation/input_manager');

SC.Responder = SC.Object.extend({
    
  
  acceptsFirstResponder: false,
  
  nextResponder: null,
  
  isFirstResponder: false,
  
  becomeFirstResponder: function() {
    if (!this.get('acceptsFirstResponder')) return false ;
    if (!SC.window) return false ;
    SC.window.set('firstResponder',this) ;
  },
  
  resignFirstResponder: function() {
    if (!SC.window) return false;
    if (SC.window.get('firstResponder') == this) {
      SC.window.set('firstResponder',null) ;
    }
  },
  
  noResponderFor: function() {},
  
  didBecomeFirstResponder: function() {},
  
  willLoseFirstResponder: function() {},
    
  
  inputManager: null,
  
  keyDown: function(evt) {
    var nr = this.get('nextResponder') ;
    return (nr && nr.keyDown) ? nr.keyDown(evt) : false;
  },
  
  keyUp: function(evt) {
    var nr = this.get('nextResponder') ;
    return (nr && nr.keyUp) ? nr.keyUp(evt) : false ;
  },
  
  flagsChanged: function(pressedFlags, evt) {
    var nr = this.get('nextResponder') || SC.window.get('firstResponder') ;
    return (nr && nr.flagsChanges) ? nr.flagsChanges(pressedFlags, evt) : false;
  },
  
  interpretKeyEvents: function(evts) {
    var inputManager = this.get('inputManager') || SC.Responder.inputManager;
    if (inputManager) inputManager.interpretKeyEvents(evts, this) ;
  },

  
  
  
  
  
  
  

  doCommand: function(cmd) {
    var nr = this;
    while (nr && (!nr[cmd] || !($type(nr[cmd]) == T_FUNCTION))) {
      nr = nr.get('nextResponder') ;
    }
    if (nr == null) nr = SC.window.get('defaultResponder') ;
    if (nr && nr[cmd] && ($type(nr[cmd]) == T_FUNCTION)) {
      var args = $A(arguments) ; args.shift() ;
      nr[cmd].apply(nr, args) ;
    }
  }
  
  
  

  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
}) ;

SC.Responder.mixin({
  inputManager: SC.InputManager.create()
}) ;


require('foundation/object') ;

SC.Routes = SC.Object.create({

  location: function(key,value) {
    if (value !== undefined) {
      if (value === null) value = '' ;
      
      if (typeof(value) == "object") {

        var parts = (value.route) ? value.route.split('&') : [''] ;
        var route = parts.shift() ;
        var params = {} ;
        parts.each(function(p) {
          var bits = p.split('=') ;
          params[bits[0]] = bits[1] ;
        }) ;

        for(var key in value) {
          if (!value.hasOwnProperty(key)) continue ;
          if (key != 'route') {
            params[key] = encodeURIComponent(''+value[key]) ;
          }
        }
        
        parts = [route] ;
        for(var key in params) {
          if (!params.hasOwnProperty(key)) continue ;
          parts.push([key,params[key]].join('=')) ;          
        }
        
        value = parts.join('&') ;
      }
      
      if (this._location != value) {
        this._location = value ;
        this._setWindowLocation(value) ;
      }
    }
    return this._location ;
  }.property(),
  
  ping: function() { 
    if (!this._didSetupHistory) {
      this._didSetupHistory = true ;
      this._setupHistory() ;
    }
    this._checkWindowLocation(); 
  },
  
  addRoute: function(route, func) {
    var parts = route.split('/') ;
    if (!this._routes) this._routes = SC.Routes._Route.create() ;
    this._routes.addRoute(parts,func) ;
  },
  
  gotoRoute: function(route) {
    var params = {} ; var parts, route, func ;

    this._lastRoute = route ;
    
    var parts = route.split('&') ;
    if (parts && parts.length > 0) {
      route = parts.shift() ;
      parts.each(function(part) {
        var param = part.split('=') ;
        if (param && param.length > 1) params[param[0]] = decodeURIComponent(param[1]) ;
      }) ;
    } else route = '' ;
    
    parts = route.split('/') ;
    
    if (!this._routes) this._routes = SC.Routes._Route.create() ;
    func = this._routes.functionForRoute(parts,params) ;
    if (func) func(params) ;
    else console.log('could not find route for: "'+route+'"') ;
  },
  
  
  init: function() {
    arguments.callee.base.call(this) ;
    if (SC.isSafari() && !SC.isSafari3()) {
      Object.extend(this,this.browserFuncs.safari) ;  
    } else if (SC.isIE()) {
      Object.extend(this,this.browserFuncs.ie) ;  
    }
    this._didSetupHistory = false ;
  },
  
  browserFuncs: {
    
    safari: {

      _setupHistory: function() {
        var cloc = location.hash ;
        cloc = (cloc && cloc.length > 0) ? cloc.slice(1,cloc.length) : '' ;
        this._cloc = cloc ;

        this._backStack = [] ;
        this._backStack.length = history.length ;
        this._backStack.push(cloc) ;

        this._forwardStack = [] ;

        this._boundChecker = this._checkWindowLocation.bind(this) ;
        this.timer = setTimeout(this._boundChecker,1000) ;
      },
      
      _checkWindowLocation: function() { 
        var historyDidChange = (history.length - this._lastLength) != 0;
        var delta = (historyDidChange) ? (history.length - this._backStack.length) : 0 ;
        this._lastLength = history.length ;

        if (historyDidChange) console.log('historyDidChange') ;
        
        if (delta) {
          if (delta < 0) { // back button has been pushed
            
            this._forwardStack.push(this._cloc) ; 
            
            for(var i=0; i < Math.abs(delta+1);i++) {
              this._forwardStack.push(this._backStack.pop()) ;
            }
            
            this._cloc = this._backStack.pop() ;
            
            
          } else { // forward button has been pushed
            
            this._backStack.push(this._cloc) ;
            
            for(var i=0; i < (delta-1); i++) {
              this._backStack.push(this._forwardStack.pop()) ;
            }
            
            this._cloc = this._forwardStack.pop() ;
          }

        } else if (historyDidChange && this._locationDidChange) {
          this.gotoRoute(this._cloc) ;
          this._locationDidChange = false ;
        }

        var cloc = this._cloc ;
        var loc = this.get('location') ;
        if (cloc != loc) {
          this.set('location',(cloc) ? cloc : '') ;
          this.gotoRoute(cloc) ;
        }
        
        this.timer = setTimeout(this._boundChecker,50) ; 
      },

      _setWindowLocation: function(loc) {        
        var cloc = this._cloc ;
        if (cloc != loc) {
          this._backStack.push(this._cloc) ;
          this._forwardStack.length = 0 ;
          this._cloc = loc ;          
          location.hash = (loc && loc.length > 0) ? loc : '' ;
          this._locationDidChange = true ;
        }
      }            
      
    },
    
    ie: {
      _setupHistory: function() {
        this._boundChecker = this._checkWindowLocation.bind(this) ;
      },

      _checkWindowLocation: function() {
        var loc = this.get('location') ;
        var cloc = location.hash ;
        cloc = (cloc && cloc.length > 0) ? cloc.slice(1,cloc.length) : '' ;
        if (cloc != loc) this.set('location',(cloc) ? cloc : '') ;
        this.timer = setTimeout(this._boundChecker,100) ;
      },

      _setWindowLocation: function(loc) {
        var cloc = location.hash ;
        cloc = (cloc && cloc.length > 0) ? cloc.slice(1,cloc.length) : '' ;
        if (cloc != loc) {
          location.hash = (loc && loc.length > 0) ? loc : '#' ;
        }
        this.gotoRoute(loc) ;
      }
    }
  },
  
  _setupHistory: function() {
    this._boundChecker = this._checkWindowLocation.bind(this) ;
    this.timer = setTimeout(this._boundChecker,1000) ;
  },
  
  _checkWindowLocation: function() {
    var loc = this.get('location') ;
    var cloc = location.hash ;
    cloc = (cloc && cloc.length > 0) ? cloc.slice(1,cloc.length) : '' ;
    if (cloc != loc) this.set('location',(cloc) ? cloc : '') ;
    this.timer = setTimeout(this._boundChecker,100) ;
  },
  
  _setWindowLocation: function(loc) {
    var cloc = location.hash ;
    cloc = (cloc && cloc.length > 0) ? cloc.slice(1,cloc.length) : '' ;
    if (cloc != loc) {
      location.hash = (loc && loc.length > 0) ? loc : '#' ;
    }
    this.gotoRoute(loc) ;
  },
  
  _routes: null,
  
  _Route: SC.Object.extend({
    
    _func: null,
    
    _static: null,
    
    _dynamic: null,
    
    _wildcard: null,
    
    addRoute: function(parts, func) {
      if (!parts || parts.length == 0) {
        this._func = func ;
        
      } else {
        var part = parts.shift() ; // get next route.
        var nextRoute = null ;
        switch(part.slice(0,1)) {
          
          case ':':
            part = part.slice(1,part.length) ;
            var routes = this._dynamic[part] || [] ;
            nextRoute = SC.Routes._Route.create() ;
            routes.push(nextRoute) ;
            this._dynamic[part] = routes ;
            break ;
            
          case '*':
            part = part.slice(1,part.length) ;
            this._wildcard = part ;
            this._func = func ;
            break ;
            
          default:
            var routes = this._static[part] || [] ;
            nextRoute = SC.Routes._Route.create() ;
            routes.push(nextRoute) ;
            this._static[part] = routes ;
        }
        
        if (nextRoute) nextRoute.addRoute(parts,func) ;
      }
    },
    
    functionForRoute: function(parts, params) {
      if (!parts || parts.length == 0) {
        return this._func ;
        
      } else {
        var part = parts.shift() ;
        var routes, nextRoute, ret, loc ;
        
        routes = this._static[part] ;
        if (routes) for(loc=0;(loc < routes.length) && (ret==null);loc++) {
          var clone = parts.slice() ;
          ret = routes[loc].functionForRoute(clone, params) ;
        }
        
        if (ret == null) for(var key in this._dynamic) {
          routes = this._dynamic[key] ;
          if (routes) for(loc=0;(loc<routes.length) && (ret == null);loc++) {
            var clone = parts.slice() ;
            ret = routes[loc].functionForRoute(clone,params) ;
            
            if (ret && params) params[key] = part ;
          }
          if (ret) break ; 
        }
        
        if ((ret == null) && this._wildcard) {
          parts.unshift(part) ;
          if (params) params[this._wildcard] = parts.join('/') ;
          ret = this._func ;
        }
        
        return ret ;
      }
    },
    
    init: function() {
      arguments.callee.base.call(this) ;
      this._static = {} ; this._dynamic = {} ;
    }
  })
  
}) ;


require('Core') ;

SC.runLoop = SC.Object.create({
  
  schedule: function() {
    var args = $A(arguments) ; 
    var obj = args.shift() ; 
    var method = obj[args.shift()];
    var delay = args.pop() ;
    var func = function() { method.apply(obj, args); }
    return setTimeout(func, delay) ;  
  }
  
}) ;

require('Core') ;


SC.Server = SC.Object.extend({
  
  prefix: null,
  
  urlFormat: '/%@/%@',
  
  preload: function(clientData) {
    if ((!clientData) || (clientData.size == 0)) return ;
    this.refreshRecordsWithData(clientData,SC.Record,null,false);
  },
  
  request: function(resource, verb, ids, params, method) {

    if (!params) params = {} ;
    var opts = {} ;
    var onSuccess = params.onSuccess; delete params.onSuccess;
    var onNotModified = params.onNotModified; delete params.onNotModified ;
    var onFailure = params.onFailure ; delete params.onFailure ;
    var context = params.requestContext ; delete params.requestContext ;
    var cacheCode = params.cacheCode; delete params.cacheCode ;

    var idPart = '' ;
    if (ids) if (ids.length > 1) {
      params.ids = [ids].flatten().join(',') ;
    } else if (ids.length == 1) {
      idPart = '/' + ids[0] ;
    }
    
    var parameters = this._toQueryString(params) ;
    if (parameters && parameters.length > 0) opts.parameters = parameters ;
    
    if (cacheCode) opts.requestHeaders = ['Sproutit-Cache',cacheCode] ;
    opts.method = method || 'get' ;
    var url = this.urlFormat.format(resource,verb) + idPart;
    var request = null ; //will container the ajax request
    
    opts.onSuccess = function(transport) {
      var cacheCode = request.getHeader('Last-Modified') ;
      if ((transport.status == '200') && (transport.responseText == '304 Not Modified')) {
        if (onNotModified) onNotModified(transport.status, transport, cacheCode,context);
      } else {
        if (onSuccess) onSuccess(transport.status, transport, cacheCode,context);
      }
    } ;
    
    opts.onFailure = function(transport) {
      var cacheCode = request.getHeader('Last-Modified') ;
      if (onFailure) onFailure(transport.status, transport, cacheCode,context);
    } ; 
    
    request = new Ajax.Request(url,opts) ;
  },



  
  listFor: function(opts) {
    var recordType = opts.recordType ;
    var resource = recordType.resourceURL() ;
    if (!resource) return false ;
    
    var order = opts.order || 'id' ;
    if (!(order instanceof Array)) order = [order] ;
    order = order.map(function(str){
      return str.decamelize().toLowerCase().replace(/-/g,'_') ; //rubyify
    }).join(',') ;

    params = {} ;
    if (opts.conditions) {
      var conditions = this._decamelizeData(opts.conditions) ;
      for(var key in conditions) {
        params[key] = conditions[key] ;
      }
    }
    
    params.requestContext = opts ;
    params.onSuccess = this._listSuccess.bind(this) ;
    params.onNotModified = this._listNotModified.bind(this) ;
    params.onFailure = this._listFailure.bind(this) ;
    if (opts.cacheCode) params.cacheCode = opts.cacheCode ;
    if (opts.offset) params.offset = opts.offset;
    if (opts.limit) params.limit = opts.limit ;
    if (order) params.order = order ;
    
    this.request(resource,'list',null,params) ;
  },
  
  _listSuccess: function(status, transport, cacheCode, context) {
    var json = eval('json='+transport.responseText) ;
    if (!json) { console.log('invalid json!'); return; }
    
    if (json.records) {
      this.refreshRecordsWithData(json.records,context.recordType,cacheCode,false);
    }
    
    var recs = (json.ids) ? json.ids.map(function(guid) {
      return SC.Store.getRecordFor(guid,context.recordType) ;
    }) : [] ;
    
    if (context.callback) context.callback(recs,json.count,cacheCode) ;
  },
  
  _listNotModified: function(status, transport, cacheCode, context) {
    if (context.callback) context.callback(null,null,null) ;
  },
  
  _listFailure: function(status, transport, cacheCode, records) {
    console.log('listFailed!') ;
  },
  
    
  
  createRecords: function(records) { 
    if (!records || records.length == 0) return ;

    records = this._recordsByResource(records) ; // sort by resource.
    for(var resource in records) {
      if (resource == '*') continue ;
      
      var curRecords = records[resource] ;

      var server = this ; var context = {} ;
      var data = curRecords.map(function(rec) {
        var recData = server._decamelizeData(rec.getPropertyData()) ;
        recData._guid = rec._guid ;
        context[rec._guid] = rec ;
        rec.set('newRecord',false) ;
        return recData ;
      }) ;

      this.request(resource,'create',null,{
        requestContext: context, 
        onSuccess: this._createSuccess.bind(this),
        onFailure: this._createFailure.bind(this),
        records: data
      },'post') ;
    }
  },

  _createSuccess: function(status, transport, cacheCode, context) {
    var json = eval('json='+transport.responseText) ;
    if (!(json instanceof Array)) json = [json] ;
    
    if (!context) context = {} ;
    json.each(function(data) {
      var guid = data._guid ;
      var rec = (guid) ? context[guid] : null ;
      if (rec) {
        var pk = rec.get('primaryKey') ;
        var dataKey = (pk == 'guid') ? 'id' : pk.decamelize().toLowerCase().replace(/\-/g,'_') ;
        rec.set(pk,data[dataKey]) ;
      }
    }) ;
    
    this.refreshRecordsWithData(json,context._recordType,cacheCode,true) ;
  },
  
  _createFailure: function(status, transport, cacheCode, records) {
    console.log('createFailed!') ;
  },
  

  
  refreshRecords: function(records) {
    if (!records || records.length == 0) return ;

    records = this._recordsByResource(records) ; // sort by resource.
    for(var resource in records) {
      if (resource == '*') continue ;
      
      var curRecords = records[resource] ;
      
      var cacheCode = null ; var ids = [] ; var context = {} ;
      var primaryKey = curRecords[0].get('primaryKey') ; // assumes all the same
      curRecords.each(function(r) {
        cacheCode = cacheCode || r._cacheCode ;
        var key = r.get(primaryKey);
        if (key) { ids.push(key); context[key] = r; }
      });
      context._recordType = curRecords[0].recordType ; // default rec type.
      
      this.request(resource,'show',ids,{
        requestContext: context, 
        cacheCode: ((cacheCode=='') ? null : cacheCode),
        onSuccess: this._refreshSuccess.bind(this),
        onFailure: this._refreshFailure.bind(this)
      }) ;
    }
  },
  
  _refreshSuccess: function(status, transport, cacheCode, context) {
    var json = eval('json='+transport.responseText) ;
    if (!(json instanceof Array)) json = [json] ;
    this.refreshRecordsWithData(json,context._recordType,cacheCode,true) ;
  },
  
  _refreshFailure: function(status, transport, cacheCode, records) {
    console.log('refreshFailed!') ;
  },
  
  
  commitRecords: function(records) {
    if (!records || records.length == 0) return ;

    records = this._recordsByResource(records) ; // sort by resource.
    for(var resource in records) {
      if (resource == '*') continue ;
      
      var curRecords = records[resource] ;

      var server = this ;
      var data = curRecords.map(function(rec) {
        return server._decamelizeData(rec.getPropertyData()) ;
      }) ;

      this.request(resource,'update',null,{
        requestContext: records, 
        onSuccess: this._commitSuccess.bind(this),
        onFailure: this._commitFailure.bind(this),
        records: data
      },'post') ;
    }
  },
  
  _commitSuccess: function(status, transport, cacheCode, context) {
    var json = eval('json='+transport.responseText) ;
    if (!(json instanceof Array)) json = [json] ;
    this.refreshRecordsWithData(json,context._recordType,cacheCode,true) ;
  },
  
  _commitFailure: function(status, transport, cacheCode, records) {
    console.log('commitFailed!') ;
  },
  
  
  destroyRecords: function(records) {
    if (!records || records.length == 0) return ;

    records = this._recordsByResource(records) ; // sort by resource.
    for(var resource in records) {
      var curRecords = records[resource] ;

      if (resource == '*') {
        curRecords.each(function(rec){
          rec.set('isDeleted',true) ;
          SC.Store.removeRecord(rec) ;
        });
        continue ;
      }

      var ids = [] ; var key ;
      var primaryKey = curRecords[0].get('primaryKey') ;

      curRecords.each(function(rec) {
        if ((key = rec.get(primaryKey)) && (!rec.get('newRecord'))) {
          ids.push(key) ; 
        }
        rec.set('isDeleted',true) ;
        SC.Store.removeRecord(rec) ;        
      }) ;

      if (ids && ids.length > 0) this.request(resource,'destroy',ids,{
        requestContext: records,
        onSuccess: this._destroySuccess.bind(this),
        onFailure: this._destroyFailure.bind(this)
      },'post') ;
    }
  },

  _destroySuccess: function(status, transport, cacheCode, records) {
    console.log('destroySuccess!') ;
  },

  _destroyFailure: function(status, transport, cacheCode, records) {
    console.log('destroyFailed!') ;
  },


  refreshRecordsWithData: function(dataAry,recordType,cacheCode,loaded) {
    var server = this ;
    dataAry = dataAry.map(function(data) {
      
      data = server._camelizeData(data) ;

      if (data.id) { data.guid = data.id; delete data.id; }

      if (data.type) {
        var recordName = data.type.capitalize()
        if (server.prefix) for(var prefixLoc=0;prefixLoc < server.prefix.length; prefixLoc++) {
          var namespace = window[server.prefix[prefixLoc]] ;
          if (namespace) data.recordType = namespace[recordName] ;
          if (data.recordType) break ;
        } else data.recordType = window[recordName] ;
        if (!data.recordType) console.log('skipping undefined recordType:'+recordName) ;
      } else data.recordType = recordType ;
      if (!data.recordType) return null; // could not process.

      return data ;
    }).compact() ;
    
    SC.Store.updateRecords(dataAry,server,recordType,loaded) ;
  },

  
  _recordsByResource: function(records) {
    var ret = {} ;
    records.each(function(rec) {
      var recs = ret[rec.resourceURL || '*'] || [] ;
      recs.push(rec) 
      ret[rec.resourceURL || '*'] = recs ;
    }) ;
    return ret ;
  },
  
  _camelizeData: function(data) {
    if (data == null) return data ;
    
    var that = this ;
    if (data instanceof Array) return data.map(function(d){
      return that._camelizeData(d) ;
    }) ;

    if (typeof(data) == "object") {      
      var ret = {} ;
      for(var key in data) {
        var value = that._camelizeData(data[key]) ;
        if (key == 'id') key = 'guid' ; 
        ret[key.replace(/_/g,'-').camelize()] = value ;
      }
      return ret ;    
    }
    
    return data ;
  },
  
  _decamelizeData: function(data) {
    if (data == null) return data ;
    
    var that = this ;
    if (data instanceof Array) return data.map(function(d){
      return that._decamelizeData(d) ;
    }) ;

    if (typeof(data) == "object") {      
      var ret = {} ;
      for(var key in data) {
        var value = that._decamelizeData(data[key]) ;
        if (key == 'guid') key = 'id' ; 
        ret[key.decamelize().toLowerCase().replace(/\-/g,'_')] = value ;
      }
      return ret ;    
    }
    
    return data ;
  },
  
  _toQueryString: function(params,rootKey) {

    if (params == null) {
      return rootKey + '=';
      
    } else if (params instanceof Array) {
      var ret = [] ;
      for(var loc=0;loc<params.length;loc++) {
        var key = (rootKey) ? (rootKey + '['+loc+']') : loc ;
        ret.push(this._toQueryString(params[loc],key)) ;
      }
      return ret.join('&') ;
      
    } else if (typeof(params) == "object") {
      var ret = []       
      for(var cur in params) {
        var key = (rootKey) ? (rootKey + '['+cur+']') : cur ;
        ret.push(this._toQueryString(params[cur],key)) ;
      }
      return ret.join('&') ;
      
    } else return [rootKey,params].join('=') ;
  }
    
}) ;


require('foundation/object') ;
require('foundation/responder') ;
require('foundation/node_descriptor') ;
require('foundation/binding');
require('foundation/path_module');

BENCHMARK_OUTLETS = NO ;
SC.FIXED = 'fixed';
SC.FLEXIBLE = 'flexible';

SC.View = SC.Responder.extend(SC.PathModule, {


  insertBefore: function(view, beforeView) { 
    this._insertBefore(view,beforeView,true);
  },
  
  _insertBefore: function(view, beforeView, updateDom) {
    if (beforeView) {
      if (beforeView.parentNode != this) throw "insertBefore() beforeView must belong to the receiver" ;
      if (beforeView == view) throw "insertBefore() views cannot be the same";
    }
    
    if (view.parentNode) view.removeFromParent() ;
    this.willAddChild(this, beforeView) ;
    view.willAddToParent(this, beforeView) ;
    
    if (beforeView) {
      view.set('previousSibling', beforeView.previousSibling) ;
      view.set('nextSibling', beforeView) ;
      beforeView.set('previousSibling', view) ;      
    } else {
      view.set('previousSibling', this.lastChild) ;
      view.set('nextSibling', null) ;
      this.set('lastChild', view) ;
    }

    if (view.previousSibling) view.previousSibling.set('nextSibling',view);
    if (view.previousSibling == null) this.set('firstChild',view) ;
    view.set('parentNode', this) ;

    if (updateDom) {
      var beforeElement = (beforeView) ? beforeView.rootElement : null;
      (this.containerElement || this.rootElement).insertBefore(view.rootElement,beforeElement);

      this._rebuildChildNodes();
    }

    view.didAddToParent(this, beforeView) ;
    this.didAddChild(view, beforeView) ;
    
    return this ;
  },
  
  removeChild: function(view) {
    if (!view) return ;
    if (view.parentNode != this) throw "removeChild: view must belong to parent";
    
    view.willRemoveFromParent() ;
    this.willRemoveChild(view) ;
    
    if (view.previousSibling) {
      view.previousSibling.set('nextSibling', view.nextSibling);
    } else this.set('firstChild', view.nextSibling) ;
    
    if (view.nextSibling) {
      view.nextSibling.set('previousSibling', view.previousSibling) ;
    } else this.set('lastChild', view.previousSibling) ;

    var el = (this.containerElement || this.rootElement);
    if (el && (view.rootElement.parentNode == el) && (el != document)) {
      el.removeChild(view.rootElement);
    }
    
    this._rebuildChildNodes();
    
    view.set('nextSibling', null);
    view.set('previousSibling', null);
    view.set('parentNode', null) ;
    view.didRemoveFromParent(this) ;
    this.didRemoveChild(view);
  },
  
  replaceChild: function(view, oldView) {
    this.insertBefore(view,oldView) ; this.removeChild(oldView) ;
  },
  
  removeFromParent: function() {
    if (this.parentNode) this.parentNode.removeChild(this) ;    
  },
  
  appendChild: function(view) {
    this.insertBefore(view,null) ;    
  },
  
  childNodes: [],
  
  firstChild: null,
  
  lastChild: null,
  
  nextSibling: null,

  previousSibling: null,

  parentNode: null,
  
  clear: function() {
    while(this.firstChild) this.removeChild(this.firstChild) ;
  },
  
  willAddToParent: function(parent, beforeView) {},
  
  didAddToParent: function(parent, beforeView) {},
  
  willRemoveFromParent: function() {},
  
  didRemoveFromParent: function(oldParent) {},

  willAddChild: function(child, beforeView) {},
  
  didAddChild: function(child, beforeView) {},
  
  willRemoveChild: function(child) {},
  
  didRemoveChild: function(child) {},
  
  nextResponder: function() { 
    return this.parentNode; 
  }.property('parentNode'),
  

  classNames: function() { 
    return Element.classNames(this.rootElement); 
  }.property(),
  
  hasClassName: function(className) {
    var ret = Element.hasClassName(this.rootElement,className) ;
    this.propertyDidChange('classNames') ;
    return ret ;
  },

  addClassName: function(className) {
    var ret = Element.addClassName(this.rootElement,className) ;
    this.propertyDidChange('classNames') ;
    return ret ;
  },
  
  removeClassName: function(className) {
    var ret = Element.removeClassName(this.rootElement,className) ;
    this.propertyDidChange('classNames') ;
    return ret ;
  },

  setClassName: function(className, flag) {
    (!!flag) ? this.addClassName(className) : this.removeClassName(className);
  },
  
  toggleClassName: function(className) {
    var ret = Element.toggleClassName(this.rootElement,className) ;
    this.propertyDidChange('classNames') ;
    return ret ;
  },

  scrollTo: function() {
    Element.scrollTo(this.rootElement) ;
  },

  getStyle: function(style) {
    return Element.getStyle(this.rootElement,style) ;
  },

  setStyle: function(styles, camelized) {
    return Element.setStyle(this.rootElement, styles, camelized) ;
  },

  update: function(html) {
    Element.update((this.containerElement || this.rootElement),html) ;
    this.propertyDidChange('asHTML') ;
  },

  getAttribute: function(attrName) {
    return Element.readAttribute(this.rootElement,attrName) ;
  },
  
  setAttribute: function(attrName, value) {
    this.rootElement.setAttribute(atrrName, value) ;
  },
  
  hasAttribute: function(attrName) {
    return Element.hasAttribute(this.rootElement, attrName) ;
  },
  
  
  rootElement: null,
  
  containerElement: null,

  
  isPositioned: false,

  changePositionObserver: function() {
    var isPositioned = this.get('isPositioned') ;
    if (this._wasPositioned == isPositioned) return ;
    
    if (isPositioned) {
      var el = this.rootElement;
      this.cacheFrame();
      
      this.setStyle({ 
        position: 'absolute',
        top: Math.floor(this._frame.y) + 'px',
        left: Math.floor(this._frame.x) + 'px',
        width: Math.floor(this._frame.width) + 'px',
        height: Math.floor(this._frame.height) + 'px'
      }) ;
      
    } else {
      var el = this.rootElement;
      el.style.position = 
      el.style.top = 
      el.style.left = 
      el.style.width = 
      el.style.height = '' ;
      this._frame = null ;
    }
    this._wasPositioned = isPositioned ;
  }.observes('isPositioned'),
  
  cacheFrame: function() {
    if (this._frame || this._frameCached) return ; // don't cache twice
    
    var el = this.rootElement ;
    this._frame = Element.getDimensions(el);
    this._frame.x = el.offsetLeft ;
    this._frame.y = el.offsetTop ;
    this._frameCached = true ;
  },
  
  flushFrameCache: function() {
    if (this._frameCached) {
      this._frame = null ;
      this._frameCached = false;
    }
  },
  
  frame: function(key, value) {
    var f ;
    if (!this._frame) {
      var el = this.rootElement;
      f = Element.getDimensions(el);
      f.x = el.offsetLeft ;
      f.y = el.offsetTop ;
    } else f = this._frame ;

    if (value !== undefined) {
      var oldSize = null ;
      if ((f.width != value.width) || (f.height != value.height)) {
        oldSize = { width: f.width, height: f.height } ;
      }
      
      f = this._frame = value ;

      if (oldSize) this.resizeChildrenWithOldSize(oldSize) ;

      this.setStyle({ 
        top:    Math.floor(this._frame.y) + 'px',
        left:   Math.floor(this._frame.x) + 'px',
        width:  Math.floor(this._frame.width) + 'px',
        height: Math.floor(this._frame.height) + 'px'
      }) ;
    }
    
    return f ;
  }.property(),
  
  size: function(key, value) {
    var f = Object.clone(this.get('frame')) ;
    if (value !== undefined) {
      f.width = value.width ;
      f.height = value.height ;
      this.set('frame',f) ;
    }
    return { width: f.width, height: f.height } ;
  }.property('frame'),
  
  origin: function(key, value) {
    var f = Object.clone(this.get('frame')) ;
    if (value !== undefined) {
      f.x = value.x ;
      f.y = value.y ;
      this.set('frame',f) ;
    }
    return { x: f.x, y: f.y } ;
  }.property('frame'),
  
  resizeChildrenWithOldSize: function(oldSize) {
    var child = this.get('firstChild') ;
    while(child) {
      child.resizeWithOldParentSize(oldSize) ;
      child = child.get('nextSibling') ;
    }
  },
  
  resizeWithOldParentSize: function(oldSize) {
    var opts = this.get('resizeOptions') ;
    
    if (opts == null) {
      if (this.firstChild) {
        var oldSize = (this._frame) ? { width: this._frame.width, height: this._frame.height } : this.get('size') ;
        this.resizeChildrenWithOldSize(oldSize) ;
      }
      return ;
    }

    if (this.get('isPositioned')) this.set('isPositioned',true) ;
    
    var f = Object.clone(this.get('frame')) ;
    var newSize = this.get('parentNode').get('size') ;
    
    var adjust = function(props, apts, newSize, oldSize) {
      var loc ;
      
      var dims = [f[apts[0]], f[apts[1]]] ;
      dims.push(oldSize - (dims[0] + dims[1])) ;
      
      for(loc=0;loc < 3;loc++) {
        if (opts[props[loc]] == SC.FIXED) {
          newSize -= dims[loc]; oldSize -= dims[loc] ;
        }
      }
      
      for(loc=0;loc < 2; loc++) {
        if (opts[props[loc]] == SC.FLEXIBLE) {
          f[apts[loc]] = newSize * dims[loc] / oldSize ;
        }
      }
    };
    
    
    adjust(['left','width','right'], ['x','width'], newSize.width, oldSize.width) ;

    adjust(['top','height','bottom'], ['y','height'], newSize.height, oldSize.height) ;

    this.set('frame',f) ;
  },
  
  resizeOptions: null,
  
  
  isVisible: function(key,value) {
    if (!(value === undefined)) {
      this._isVisible = value ;
      (value) ? this.showView() : this.hideView() ;
    }
    return this._isVisible ;
  }.property(),
  
  _isVisible: true,
  isVisibleBindingDefault: SC.Binding.Flag,
  
  localize: false,
  
  toolTip: '',

  emptyElement: "<div></div>",
  
  isPanel: false,

  isModal: true,
  
  isAnimationEnabled: true,

  animateVisible: function(key, value) {
    if (value !== undefined) return this.set('isAnimationEnabled',value) ;
    return this.get('isAnimationEnabled');
  }.property(),

  lastIsAnimationEnabled: true,
  
  originalAnimationDuration: 0,
    
  transitionTo: function(target,animator,opts) {
    var animatorOptions = opts || {} ;

    if (animator && !animator._isAnimator) {
      var finalStyle = animator ;
      if (!this.get("isAnimationEnabled"))  {
        animatorOptions = Object.clone(animatorOptions) ;
        animatorOptions.duration = 1; 
      }
      if (animatorOptions.duration) animatorOptions.duration = parseInt(animatorOptions.duration,0) ;

      animator = Animator.apply(this.rootElement, finalStyle, animatorOptions);
      animator._isAnimator = true ;
    }

    if (animator) {
      animator.jumpTo(animator.state) ;
      animator.seekTo(target) ;
    }
    return animator ;    
  },
  
  asHTML: function(key, value) {
    if (value !== undefined) {
      this.update(value) ;
      
      if (SC.isSafari() && !SC.isSafari3()) {
        var el = (this.containerElement || this.rootElement) ; var reps = 0 ;
        var f = function() {
          el.innerHTML = '' ; el.innerHTML = value ;
          if ((reps++ < 5) && (value.length>0) && (el.innerHTML == '')) setTimeout(f,1) ;
        } ;
        f() ;        

      } else this.innerHTML = value;
    } else value = (this.containerElement || this.rootElement).innerHTML ;
    return value ;
  }.property(),
  
  asText: function(key, value) {
    if (value !== undefined) {
      if (value == null) value = '' ;
      this.asHTML(key,value.toString().escapeHTML()) ;
    }
    return this.asHTML().unescapeHTML() ;
  }.property(),

  init: function() {
    arguments.callee.base.call(this) ;

    var r = SC.idt.active ; var idtStart ; var idtSt ;
    if (r) { idtSt = new Date().getTime(); }
    this.configureOutlets() ;
    if (r) { SC.idt.conf_t += ((new Date().getTime()) - idtSt); }

    var toolTip = this.get('toolTip') ;
    if(toolTip && (toolTip != '')) this._updateToolTipObserver();     
  },
  
  configureOutlets: function() {
    
    if (!this.outlets || (this.outlets.length <= 0)) return ; 

    this.beginPropertyChanges(); // bundle changes
    for(var oloc=0;oloc < this.outlets.length;oloc++) {
      var view = this.outlet(this.outlets[oloc]) ;
      
      if (view && view.rootElement && view.rootElement.parentNode) {
        this._insertBefore(view,null,false) ;
      }
      this._rebuildChildNodes() ; // this is not done with _insertBefore.
    }    
    this.endPropertyChanges() ;
  },

  showView: function() {
    if (this.get('isPanel')) {
      SC.page.get('panels').showPanel(this) ;
    } else if (this.visibleAnimation && (this.isAnimationEnabled !== false)) {
        this._isAnimationEnabledTo(1.0) ;
    }
        
    Element.show(this.rootElement) ;
    this.removeClassName('hidden') ;
    return this ;
  },

  hideView: function() {
    if (this.get('isPanel')) {
      SC.page.get('panels').hidePanel(this) ;
    } else if (this.visibleAnimation && (this.isAnimationEnabled !== false)) {
        this._isAnimationEnabledTo(0.0) ;
    } else Element.hide(this.rootElement) ;
     
    return this ;
  },


  _attachRootElement: function(el) {
    if (this.rootElement) this.rootElement._configured = null ;
    this.rootElement = el ; 
    el._configured = this._guid ;
  },
  
  _rebuildChildNodes: function() {
    var ret = [] ; var view = this.firstChild;
    while(view) { ret.push(view); view = view.nextSibling; }
    this.set('childNodes', ret) ;
  },
  
  _toolTipObserver: function() {
    var toolTip = this.get('toolTip') ;
    if (this.get('localize')) toolTip = toolTip.loc() ;
    this.rootElement.title = toolTip ;
  }.observes("toolTip"),
  
  _isAnimationEnabledTo: function(target) {
    var a ;

    if(this.get("lastIsAnimationEnabled") != this.get("isAnimationEnabled"))
    {
      this.set("lastIsAnimationEnabled", this.get("isAnimationEnabled"));
      this._visibleAnimation = null;
    }

    if (this._visibleAnimation) {
      this.transitionTo(target,this._visibleAnimation); 
    } else {
      var opts = this.visibleAnimation ;
      var style = [opts.hidden,opts.visible] ;
      opts.onComplete = 
        this._visibleAnimationComplete.bind(this,opts.onComplete) ;
      this._visibleAnimation = this.transitionTo(target,style,opts);      
    }
  },

  _visibleAnimationComplete: function(chainFunc) {
    if (this.get('isVisible')) {
    } else {
      Element.hide(this) ;
      this.addClassName('hidden') ;
    }
    if (chainFunc) chainFunc(this) ;
  },
  

  popup: function(evt) {
    var popups = SC.page.get('popups') ;
    this._hider = this.hide ;  this.hideView = popups.viewHide ;
    popups.showPanel(this,evt) ;
  },
  
  configureObserverMethods: function(methodMap) {
    for(var name in methodMap) {
      if (!methodMap.hasOwnProperty(name)) continue ;
      if (this[name]) {
        var method = this[name].bindAsEventListener(this);
        Event.observe(this.rootElement,methodMap[name],method) ;
      }
    }
  },
  
  toString: function() {
    var el = this.rootElement ;
    var attrs = el.attributes ;
    attrs = (attrs) ? $A(attrs).map(function(atr) { return [atr.nodeName,atr.nodeValue].join("="); }).join(' ') : '';
    var tagName = (!!el.tagName) ? el.tagName.toLowerCase() : 'document' ;
    return "View(<%@>)".format([tagName,attrs].join(' ')) ;
  }
    
}) ;
  
SC.View.mixin({  

  _view: {},
  
  findViewForElement: function(el) {
    var guid = el._configured ;
    return (guid) ? SC.View._view[guid] : null ;  
  },
  
  viewFor: function(el,config) {
    if (el) el = $(el) ;

    var r = SC.idt.active ; var vStart ;
    if (r) SC.idt.v_count++;

    if (r) vStart = new Date().getTime() ;
    
    if (!el) {    
      var emptyElement = this.prototype._cachedEmptyElement || this.prototype.emptyElement; 
      
      var isString = typeof(emptyElement) == 'string' ; 
      if (isString && (emptyElement.slice(0,1) != '<')) {
        var el = $sel(emptyElement) ;
        if (el) {
          this.prototype.emptyElement = emptyElement = el ;
          isString = false ;
        }
      }

      if (isString) {
        SC._ViewCreator.innerHTML = emptyElement ;
        el = $(SC._ViewCreator.firstChild) ;
        SC.NodeCache.appendChild(el) ;
        this.prototype._cachedEmptyElement = el.cloneNode(true) ;
        
      } else if (typeof(emptyElement) == "object") {
        if (emptyElement.tagName) {
          el = emptyElement.cloneNode(true) ;
        } else el = SC.NodeDescriptor.create(emptyElement) ;
      }
    }
    if (r) SC.idt.vc_t += (new Date().getTime()) - vStart ;

    if (el && el._configured) return SC.View.findViewForElement(el); 
    
    var args = $A(arguments) ; args[0] = { rootElement: el } ;
    if (r) vStart = new Date().getTime();
    var ret = new this(args,this) ; // create instance.
    if (r) SC.idt.v_t += (new Date().getTime()) - vStart;
    el._configured = ret._guid ;

    SC.View._view[ret._guid] = ret ;
    return ret ;    
  },
  
  create: function(configs) {
    var args = $A(arguments) ;
    args.unshift(null) ;
    return this.viewFor.apply(this,args) ;  
  },
  
  outletFor: function(path) {
    var view = this ;
    var _func = function() {
      if (path === null) return view.viewFor(null) ;

      
      var ret = (this.$$sel) ? this.$$sel(path) : $$sel(path) ;
      if (ret) {
        var owner = this ;
        var newRet = [] ;
        for(var loc=0;loc<ret.length;loc++) {
          newRet.push(view.viewFor(ret[loc], { owner: owner })); 
        }
        ret = newRet ;
        ret = (ret.length == 0) ? null : ((ret.length == 1) ? ret[0] : ret);
      }

      return ret ;
    } ;
    
    var func ;
    if (BENCHMARK_OUTLETS) {
      func = function() {
        var that = this ;
        return SC.Benchmark._bench(function() {
          return _func.call(that);
        }, "OUTLET(%@)".format(path)) ;
      };
    } else func = _func ;    
    func.isOutlet = true ;
    return func ;
  }  

}) ;

SC.View.elementFor = SC.View.viewFor ; // Old Sprout Compatibility.

SC._ViewCreator = document.createElement('div') ;

SC.NodeCache = document.createElement('div') ;

SC._EventObserverMethods = {
  "didClick": "click",
  "didDoubleClick": "dblclick",
  "didMouseDown": "mousedown",
  "didMouseUp": "mouseup",
  "didMouseOver": "mouseover",
  "didMouseOut": "mouseout",
  "mouseDidMove": "mousemove",
  "mouseDidDrag": "mousedrag",
  "didKeyDown": "keydown",
  "didKeyUp": "keyup",
  "didKeyPress": "keypress"
} ;


require('views/view') ;

SC.ContainerView = SC.View.extend({
  
  content: null,
  contentBindingDefault: SC.Binding.Single,

  replaceContent: function(newContent) {
    this.clear() ;
    this.appendChild(this.get('content')) ;
  },
  
  _contentObserver: function() {
    this.replaceContent(this.get('content'));
  }.observes('content')
  
}) ;


require('views/view') ;
require('views/container') ;

SC.PanelView = SC.View.extend({
  
  emptyElement: '<div id="panels" class="panels"><div class="overlay"></div></div>',

  wrapperView: SC.ContainerView.extend({
    emptyElement: '<div class="panel"></div>'
  }),
  
  _wrapperPool: null,

  _getWrapperView: function() {
    var ret = this._wrapperPool.pop() ;
    if (ret) return ret ;
    ret = this.wrapperView.viewFor() ;

    if (ret.visibleAnimation) {
      var va = Object.clone(ret.visibleAnimation) ;
      va.onComplete = this.hidePanelDidComplete.bind(this) ;
      ret.visibleAnimation = va ;
    }

    return ret ;
  },
  

  locationFor: function(view,evt) {
    return { top: '50px', left: 'auto' } ;
  },
  
  showPanel: function(view,evt) {
    var wrapperView = this._getWrapperView() ;
    
    wrapperView.set('animateVisible',false) ;
    wrapperView.set('isVisible',false) ;
    wrapperView.set('content',view) ; // set the popup view.
    view._wrapperView = wrapperView ;

    this.nowShowing.push(view) ;
    this.appendChild(wrapperView) ;
    this.set('isVisible',true) ; // show panels
    
    wrapperView.setStyle({ visibility: 'hidden' }) ;
    wrapperView.set('isVisible',true) ; // show the panel
    
    var dim = Element.getDimensions(view) ;
    Element.setStyle(wrapperView, this.locationFor(view,evt)) ;

    wrapperView.set('isVisible',false) ;
    wrapperView.setStyle({ width: dim.width+'px', visibility: 'visible' });
    
    wrapperView.set('animateVisible',true);
    wrapperView.set('isVisible',true) ;
  },
  
  hidePanel: function(view) {
    var didHideWrapperView = null ;

    if (view._wrapperView) {
      if (view._wrapperView.visibleAnimation) {
      } else {
        didHideWrapperView = view._wrapperView ;
      }
      view._wrapperView.set('isVisible',false) ;
      view._wrapperView = null ;
    }
    
    this.nowShowing = this.nowShowing.without(view) ;
    if (didHideWrapperView) this.hidePanelDidComplete(didHideWrapperView) ;
  },

  hidePanelDidComplete: function(wrapperView) {
    if (wrapperView.get('isVisible') != false) return ;
    if (wrapperView) {
      wrapperView.set('content',null) ;
      this._wrapperPool.push(wrapperView) ;
    }
    if (this.nowShowing.length <= 0) this.set('isVisible',false);
  },
  
  init: function() {
    arguments.callee.base.call(this) ;
    this.nowShowing = [] ;
    this._wrapperPool = [] ;
  },
  
  
  showView: function() {
    var bodyNode = $tag('body');
    if (this.parentNode != bodyNode) bodyNode.appendChild(this) ;
    Element.setStyle(this, { zIndex: '10000', visibility: 'visible' }) ;
    if (!SC.isIE7() && bodyNode) bodyNode.addClassName('under-panel') ;
  },
  
  hideView: function() {
    var bodyNode = $('body') || $tag('body');
    Element.setStyle(this, { zIndex: '-10000', visibility: 'hidden' }) ;
    if (!SC.isIE7() && bodyNode) bodyNode.removeClassName('under-panel') ;
  },
  
  didClick: function(ev) {
    if (this.nowShowing.length == 0) return ; // nothing to do.
    
    var topPanel = this.nowShowing[this.nowShowing.length-1] ;
    var tgt = Event.element(ev) ;

    var view = topPanel._wrapperView ;
    while(tgt && (tgt != this) && (tgt != view)) tgt = tgt.parentNode ;
    
    if ((tgt != view) && (!topPanel.get('isModal'))) {
      topPanel.set('isVisible',false) ;
    }
  }
    
}) ;

SC.callOnLoad(function() { 
  if (!SC.page) SC.page = SC.Page.create() ;
  SC.page.panels = SC.PanelView.outletFor(null); 
}) ;


require('views/view') ;
require('views/container') ;
require('globals/panels') ;

SC.PopupView = SC.PanelView.extend({
  
  emptyElement: '<div id="popups" class="popups"></div>',
    
  wrapperView: SC.ContainerView.extend({
    emptyElement: '<div class="popup"></div>',
    visibleAnimation: {
      visible: 'opacity: 1.0',
      hidden: 'opacity: 0.0',
      duration: 200,
      onComplete: function(wrapperView) {
        if (!wrapperView.get('isVisible')) {
          SC.popups.hidePanelDidComplete(wrapperView) ;
        }
      }
    }
  }),
  
  locationFor: function(view,ev) {
    var x = (ev) ? (Event.pointerX(ev) - 20) : 100 ;
    var y = (ev) ? Event.pointerY(ev) : 100 ;
    var dim = Element.getDimensions(view) ;
    var screenSize = Element.getDimensions(this) ;
    
    var shift = (x+dim.width+50) - screenSize.width ;
    if (shift>0) x -= shift ;
    var shift = (y+dim.height+20) - screenSize.height ;
    if (shift>0) y -= shift ;
    
    return { left: x+'px', top: y+'px' };
  },
  
  viewHide: function() { SC.page.get('popups').hidePanel(this); }
    
}) ;

SC.callOnLoad(function() { 
  if (!SC.page) SC.page = SC.Page.create() ;
  SC.page.popups = SC.PopupView.outletFor(null); 
}) ;


require('Core') ;
require('foundation/responder') ;

SC.window = SC.View.extend({
  
  firstViewForEvent: function(evt) {
    var el = Event.element(evt) ;
    while(el && (el != document) && (!el._configured)) el = el.parentNode ;
    if (el) el = SC.View.findViewForElement(el) ;
    if (el == this) el = null ;
    return el ;
  },


  frame: function() {
    var size = this.get('size') ;
    return { x: 0, y: 0, width: size.width, height: size.height } ;
  }.property('size'),
  
  size: function() {
    if (!this._size) {
      if (window.innerHeight) {
        this._size = { 
          width: window.innerWidth, 
          height: window.innerHeight
        };
      } else if (document.documentElement && document.documentElement.clientHeight) {
        this._size = { 
          width: document.documentElement.clientWidth, 
          height: document.documentElement.clientHeight
        };
      } else if (document.body) {
        this._size = { 
          width: document.body.clientWidth, 
          height: document.body.clientHeight
        };
      }
    }
    
    return this._size ;
  }.property(),
  
  autoresizesChildViews: true,
  
  _onresize: function(evt) {
    var oldSize = Object.clone(this.get('size')) ;
    this._size = null ;
    var newSize = this.get('size') ;
    if ((newSize.width != oldSize.width) || (newSize.height != oldSize.height)) {
      this.resizeChildrenWithOldSize(oldSize) ;
    }
  },
  
  
  firstResponder: function(key,value) {
    if (value !== undefined) {
      if (this._firstResponder) {
        this._firstResponder.willLoseFirstResponder();
        this._firstResponder.set('isFirstResponder',false) ;
      }
      
      this._firstResponder = value ;
     
      if (this._firstResponder) {
        this._firstResponder.set('isFirstResponder',true) ;
        this._firstResponder.didBecomeFirstResponder() ;
      }
    }
    return this._firstResponder ;
  }.property(),
  
  defaultResponder: null,

  
  invokeFirstResponder: function(methodName) {
    var args = $A(arguments) ;
    var methodName = args.shift() ;
    var handled = false ; var func ;

    var fr = this.get('firstResponder') ;
    if (fr && (func = fr[methodName]) && ($type(func) == T_FUNCTION)) {
      handled = func.apply(fr, args) ;
    }
    if (handled) return true ;
    
    fr = this.get('defaultResponder') ;
    if (fr && (func = fr[methodName]) && ($type(func) == T_FUNCTION)) {
      handled = func.apply(fr, args) ;
    }
    
    if (!handled) {
      var fr = this.get('firstResponder') || this.get('defaultResponder') || this ;
      if (fr.noResponderFor) fr.noResponderFor(methodName) ;
    }
    
    return handled ; // EXIT POINT
  },

  noResponderFor: function() {},
  
  _lastModifiers: null,
  _handleModifierChanges: function(evt) {
    var code = evt.keyCode; 

    var m = this._lastModifiers = this._lastModifiers || { alt: false, ctrl: false, shift: false };
    var changed = false ;
    if (evt.altKey != m.alt) { m.alt = evt.altKey; changed=true; }
    if (evt.ctrlKey != m.ctrl) { m.ctrl = evt.ctrlKey; changed=true; }
    if (evt.shiftKey != m.shift) { m.shift = evt.shiftKey; changed=true;}
    if (changed) this.invokeFirstResponder('flagsChanged',m,evt);
  },
  
  _onkeydown: function(evt) {
    this._handleModifierChanges(evt) ;
    
    var code = evt.keyCode ;
    if (code == 16 || code == 17 || code == 18) return ;
    
    if (evt.altKey || evt.ctrlKey || SC.FUNCTION_KEYS[code]) {
      return !this.invokeFirstResponder('keyDown', evt) ;
    } else return true; // let normal browser processing do its thing.
  }, 

  _onkeypress: function(evt) {
    if (evt.altKey || evt.ctrlKey) return ;
    if (evt.charCode != undefined && evt.charCode == 0) return ;
    return !this.invokeFirstResponder('keyDown',evt) ;
  },
  
  _onkeyup: function(evt) {
    this._handleModifierChanges(evt) ;
    
    var code = evt.keyCode ;
    if (code == 16 || code == 17 || code == 18) return;
    
    this.invokeFirstResponder('keyUp', evt) ;
  },
  
  _mouseDownView: null,
  
  _onmousedown: function(evt) {
    var view = this.firstViewForEvent(evt) ;
    var handled = false ;
    while(view && (view != this) && !handled) {
      var func = view.mouseDown || view.didMouseDown;
      if (func) handled = func.call(view, evt) ; 
      if (!handled) view = view.nextResponder ;
    }
    if (view == this) view = null;
    this._mouseDownView = view ;
  },
  
  _onmouseup: function(evt) {
    var view ;
    if (view = this._mouseDownView) {
      var func = view.mouseUp || view.didMouseUp;
      if (func) func.call(view,evt) ;
    } 
  },
  
  _onclick: function(evt) {
    var view ;
    if (view = this._mouseDownView) {
      var func = view.click || view.didClick;
      if (func) func.call(view,evt) ;
    } else {
      view = this.firstViewForEvent(evt) ;
      var handled = false ;
      while(view && (view != this) && !handled) {
        var func = view.click || view.didClick;
        if (func) handled = func.call(view, evt) ; 
        if (!handled) view = view.nextResponder ;
      }
    }
  },

  _ondblclick: function(evt) {
    var view ;
    if (view = this._mouseDownView) {
      var func = view.dblClick || view.didDblClick;
      if (func) func.call(view,evt) ;
    } else {
      view = this.firstViewForEvent(evt) ;
      var handled = false ;
      while(view && (view != this) && !handled) {
        var func = view.dblClick || view.didDblClick;
        if (func) handled = func.call(view, evt) ; 
        if (!handled) view = view.nextResponder ;
      }
    }
  },
  
  _lastHovered: null,

  _onmouseover: function(evt) {
    var view = this.firstViewForEvent(evt) ;
    if ((view != this) && (view != null)) return ; 

    var view = this; 
    var entered = view.didMouseOver || view.mouseOver || view.mouseEntered;
    if (entered) entered.call(this, evt) ;

  },
  
  _onmouseout: function(evt) {
    var view = this.firstViewForEvent(evt) ;
    if ((view != this) && (view != null)) return ; 
    
    var view = this; 
    var exited = view.didMouseOut || view.mouseOut || view.mouseExited ;
    if (exited) exited.call(this, evt) ;
    
    if (this._lastHovered) {
      for(var loc=0; loc< this._lastHovered.length; loc++) {
        var view = this._lastHovered[loc] ;
        var exited = view.didMouseOut || view.mouseOut || view.mouseExited ;
        if (exited) exited.call(view, evt) ;
      }
    }
  },
  
  _onmousemove: function(evt) {
    var lh = this._lastHovered || [] ;
    var nh = [] ;
    var view = this.firstViewForEvent(evt) ;
    
    while(view && (view != this)) {
      var entered = view.didMouseOver || view.mouseOver || view.mouseEntered;
      var moved = view.mouseDidMove || view.mouseMoved  ;

      if (lh.include(view)) {
        if (moved) moved.call(view,evt) ;
        nh.push(view) ;
      } else {
        if (entered) entered.call(view,evt) ;
        nh.push(view) ;
      }
      
      view = view.get('nextResponder') ;
    }

    for(var loc=0; loc < lh.length; loc++) {
      view = lh[loc] ;
      var exited = view.didMouseOut || view.mouseOut || view.mouseExited ;
      if (exited && !nh.include(view)) exited.call(view, evt) ;
    }
    
    this._lastHovered = nh; 
  },
  
  setup: function() {
    console.log('setup') ;
    
    var evts = ['mousedown', 'mouseup', 'click', 'dblclick', 'keydown', 'keyup', 'keypress', 'mouseover', 'mouseout', 'mousemove', 'resize'] ;
    var win = this ;
    evts.each(function(e) {
      var func = win['_on' + e] ;
      if (SC.isIE()) {
        var target =  (e == 'resize') ? window : document ;
        if (func) Event.observe(target, e, func.bindAsEventListener(win)) ;
      } else {
        if (func) Event.observe(window, e, func.bindAsEventListener(win)) ;
      }
    });
    
    this.get('size') ; // fetch the size from the window and save it.
  }
}).viewFor(document) ;

SC.callOnLoad(SC.window.setup.bind(SC.window)) ;



















require('foundation/object') ;

SC.Record = SC.Object.extend({
  

  properties: ['guid'],
  
  primaryKey: 'guid',
  
  newRecord: false,
  
  changeCount: 0,
  
  isDeleted: false,
  

  resourceURL: null,
  
  dataSource: SC.Store,
  
  refresh: function() { 
    if (!this.get('newRecord')) this.dataSource.refreshRecords([this]); 
  },
  
  commit: function() {
    if (this.get('newRecord')) {
      this.dataSource.createRecords([this]) ;
    } else {
      this.dataSource.commitRecords([this]) ;
    }
  },
  
  destroy: function() { this.dataSource.destroyRecords([this]) ; },

  
  readAttribute: function(key) {
  	if (!this._cachedAttributes) this._cachedAttributes = {} ;
  	var ret = this._cachedAttributes[key] ;
  	if (ret === undefined) {
			var attr = this._attributes ;
			ret = (attr) ? attr[key] : undefined ;
			if (ret !== undefined) {
				var recordType = this._getRecordType(key+'Type') ;
				ret = this._propertyFromAttribute(ret, recordType) ;
			}
			this._cachedAttributes[key] = ret ;
  	}
  	return ret ;
  },

  writeAttribute: function(key, value) {
    var recordType = this._getRecordType(key+'Type') ;
    var ret = this._attributeFromProperty(value, recordType) ;
    if (!this._attributes) this._attributes = {} ;
    this._attributes[key] = ret ;
    if (this._cachedAttributes) delete this._cachedAttributes[key];  // clear cache.
    this.incrementProperty('changeCount') ;
    if (SC.Store) SC.Store.recordDidChange(this) ;
    return value ;  
  },
  
  updateAttributes: function(newAttrs, replace, isLoaded) {
    var changed = false ;
    if (this._attributes && (replace !== true)) {
      for(var key in newAttrs) {
        if (!newAttrs.hasOwnProperty(key)) continue ;
        if (!changed) changed = (this._attributes[key] != newAttrs[key]) ;
        this._attributes[key] = newAttrs[key] ;
      }
    } else {
      this._attributes = newAttrs ;
      changed = true ;
    }
    
    this._cachedAttributes = {} ; // reset cache.
    
    if (changed) {
      this.beginPropertyChanges() ;
      this.set('changeCount',0) ;
      this.set('isLoaded',isLoaded) ;
      this.allPropertiesDidChange() ;
      this.endPropertyChanges() ;

      if (SC.Store) SC.Store.recordDidChange(this) ;
    }
  },
  
  attributes: function() {
    return $H(this._attributes) ;
  }.property(),
  
  unknownProperty: function(key,value) {
    if (value !== undefined) { this.writeAttribute(key,value) ;
    } else value = this.readAttribute(key) ;
    return value ;
  },
  
  _attributeFromProperty: function(value,recordType) {
    if (value && value instanceof Array) {
      var that = this;
      return value.map(function(v) { 
        return that._attributeFromProperty(v,recordType); 
      }) ;
    } else {
      var typeConverter = this._pickTypeConverter(recordType) ;
      if (typeConverter) return typeConverter(value,'out') ;
      if (recordType) {
        return (value) ? value.get(recordType.primaryKey()) : null ;
      } else return value ;
    }
  },
  
  _propertyFromAttribute: function(value,recordType) {
    if (value && value instanceof Array) {
      var that = this;
      return value.map(function(v) { 
        return that._propertyFromAttribute(v,recordType); 
      }) ;
    } else {
      var typeConverter = this._pickTypeConverter(recordType) ;
      if (typeConverter) return typeConverter(value,'in') ;
      if (recordType) {
        return SC.Store.getRecordFor(value,recordType) ;
      } else return value ;
    }
  },
  
  _getRecordType: function(recordTypeKey) {
    var type = this[recordTypeKey] ;
    if (type && (typeof(type) == "string")) {
      type = eval(type) ; // look up type.
      if (type) this[recordTypeKey] = type ;
    }
    return type ;
  },
  
  
  valueForSortKey: function(key) { return this.get(key); },
  
  compareTo: function(object, orderBy) {
    if (!orderBy) orderBy = [this.get('primaryKey')] ;
    var ret = SC.Record.SORT_SAME ; var loc ;
    for(loc=0; (ret == SC.Record.SORT_SAME && loc<orderBy.length); loc++) {
      var key = orderBy[loc] ;
      
      var asc = true ;
      if (key.match(/ DESC$/)) { 
        asc = false; key = key.slice(0,-5); 
      } else if (key.match(/ ASC$/)) {
        asc = true; key = key.slice(0,-4); 
      }

      var keys = key.split('.') ;
      key = keys.shift() ;
      
      var a = this.valueForSortKey(key) ;
      var b = object.valueForSortKey(key) ;
      
      a = this._comparableValueFor(a,keys) ;
      b = this._comparableValueFor(b,keys) ;
      
      if (asc) {
        ret = (a<b) ? SC.Record.SORT_BEFORE : ((a>b) ? SC.Record.SORT_AFTER : SC.Record.SORT_SAME) ;
      } else {
        ret = (a>b) ? SC.Record.SORT_BEFORE : ((a<b) ? SC.Record.SORT_AFTER : SC.Record.SORT_SAME) ;
      }
    }
    return ret ;
  },
  
  _comparableValueFor: function(value, keys) {
    if (keys && keys.length > 0) {
      var key ; var loc = 0 ;
      while(value && (loc < keys.length)) {
        key = keys[loc]; 
        value = (value.get) ? value.get(key) : value[key] ;
        loc++ ;
      }
    
    } else value = (value && value._guid) ? value._guid : value ;
    return value ;
  },
  
  matchConditions: function(conditions) {
    for(var key in conditions) {
      var value = conditions[key] ;
      if (value instanceof Array) {
        var loc = value.length ; var isMatch = false ;
        while(--loc >= 0) {
          if (this.matchCondition(key,value[loc])) isMatch = true ;
        }
        if (!isMatch) return false ;
      } else if (!this.matchCondition(key,value)) return false ;      
    }
    return true ;
  },
  
  matchCondition: function(key, value) {
    var recValue = this.get(key) ;    
    var isMatch ;
    
    if (value &&  value.primaryKey) value = value.get(value.primaryKey) ;
    
    if (recValue instanceof Array) {
      var loc = recValue.length ;
      while(--loc >= 0) { 
        if (this._matchValue(recValue[loc],value)) return true; 
      }
    } else return this._matchValue(recValue,value) ;
    return false ;
  },

  _matchValue: function(recValue,value) {
    if (recValue && recValue.primaryKey && typeof(value) == "string") recValue = recValue.get(recValue.primaryKey) ;
    var stringify = (value instanceof RegExp);
    return (stringify) ? recValue.toString().match(value) : recValue==value ;        
  },
  
  
  toString: function() {
    var that = this ;  
    var ret = this.get('properties').map(function(key) {
      var value = that.get(key) ; 
      if (typeof(value) == "string") value = '"' + value + '"' ;
      if (value === undefined) value = "(undefined)" ;
      if (value === null) value = "(null)" ;
      return [key,value].join('=') ;
    }) ;
    return 'Record({ ' + ret.join(', ') + ' })' ;
  },
  
  propertyObserver: function(observing,target,key,value) {
  },
  
  _cprops: ['properties'],

  updateProperties: function(data,isLoaded) {
    var rec = this ;
    
    this.beginPropertyChanges() ;
    if (isLoaded) this.set('isLoaded',true) ;
    try {
      var loc = this.properties.length ;
      while(--loc >= 0) {
        var prop = this.properties[loc] ;
        var newValue = data[prop] ;

        
        if (newValue === null) {
          if (rec.get(prop) != null) rec.set(prop,null) ;
          
        } else if (newValue !== undefined) {
          
          var oldValue = rec.get(prop) ;            
          
          var recordType = rec.get(prop + 'Type') ;
          var typeConverter = this._pickTypeConverter(recordType) ;
          if (typeConverter) recordType = null ;
 
          var isSame ; var rec = this ;
          if (newValue instanceof Array) {
            newValue = newValue.map(function(nv) {
              return rec._convertValueIn(nv,typeConverter,recordType) ;
            }) ;
            isSame = newValue.isEqual(oldValue) ;
          } else {
            newValue = this._convertValueIn(newValue,typeConverter,recordType);
            isSame = newValue == oldValue ;
          }
          
          if (!isSame) this.set(prop,newValue) ;
           
        }
      }
    }
    
    catch(e) {
      console.log(this._guid + ': Exception raised on UPDATE: ' + e) ;
    }

    this.endPropertyChanges() ;   
    this.set('changeCount',0) ;        
  },
  
  getPropertyData: function() {
    var ret = {} ;
    var properties = this.get('properties') || []; var loc = properties.length;
    while(--loc >= 0) {
      var key = properties[loc] ;
      var value = this.get(key) ;
      var recordType = this[key + 'Type'] ;
      var typeConverter = this._pickTypeConverter(recordType) ;
      if (typeConverter) recordType = null ;
      
      if (value instanceof Array) {
        var ary = [] ;
        for(var vloc=0;vloc<value.length;vloc++) {
          var v = value[vloc] ;
          ary.push(this._convertValueOut(v,typeConverter,recordType));          
        }
        value = ary ;
      } else value = this._convertValueOut(value,typeConverter,recordType);
      
      ret[key] = value ;
    }
    return ret ;
  },
  
  _pickTypeConverter: function(recordType) {
    var typeConverter = null ;
    if (recordType && recordType.isTypeConverter) {
      typeConverter = recordType; recordType = null ;
    } else if(recordType) switch(recordType) {
      case Date:
        typeConverter = SC.Record.Date; recordType = null ;
        break ;
      case Number:
        typeConverter = SC.Record.Number; recordType = null;
        break;
      case String:
        typeConverter = null; recordType = null ;
        break ;
    }          
    return typeConverter;
  },
  
  _convertValueOut: function(value,typeConverter,recordType) {
    if (typeConverter) return typeConverter(value,'out') ;
    if (recordType) {
      return (value) ? value.get(recordType.primaryKey) : null ;
    } else return value ;
  },
  
  _convertValueIn: function(value,typeConverter,recordType) {
    if (typeConverter) return typeConverter(value,'in') ;
    if (recordType) {
      return SC.Store.getRecordFor(value,recordType) ;
    } else return value ;
  },
  
  _storeKey: function() { return this._type._storeKey(); }  
  
     
}) ;

SC.Record.mixin({

  SORT_BEFORE: -1, SORT_AFTER: 1, SORT_SAME: 0,

  find: function(guid) {
    var args ;
    if (typeof(guid) == 'object') {
      args = $A(arguments) ;
      args.push(this) ;
      var ret = SC.Store.findRecords.apply(SC.Store,args) ;
      return (ret && ret.length > 0) ? ret[0] : null ;
    } else return SC.Store._getRecordFor(guid,this) ;
  },
  
  findOrCreate: function(guid) {
    var ret = this.find(guid) ;
    if (!ret) {
      var opts = (typeof(guid) == "object") ? guid : { guid: guid } ;
      ret = this.create(opts) ;
      SC.Store.addRecord(ret) ;
    }
    return ret ;
  },
  
  findAll: function(filter) {
    if (!filter) filter = {} ;
    args = $A(arguments) ; args.push(this) ; // add type
    return SC.Store.findRecords.apply(SC.Store,args) ;  
  },
  
  collection: function(opts) {
    if (!opts) opts = {} ;
    opts.recordType = this;
    return SC.Collection.create(opts) ;
  },
  
  
  extend: function() {
    var ret = SC.Object.extend.apply(this,arguments) ;
    if (ret.coreRecordType == null) ret.coreRecordType = ret ;
    return ret ;
  },  

  _storeKey: function() {
    return (this.coreRecordType) ? this.coreRecordType._guid : this._guid ;
  },
  
  primaryKey: function() { return this.prototype.primaryKey; },

  coreRecordType: null,

  resourceURL: function() { return this.prototype.resourceURL; },
  
  hasMany: function(recordTypeString,conditionKey,opts) {
    opts = (opts === undefined) ? {} : Object.clone(opts) ;
    var conditions = opts.conditions || {} ;
    opts.conditions = conditions ;

    var privateKey = '_' + conditionKey ;
    return function() {
      if (!this[privateKey]) {
        var recordType = eval(recordTypeString);
        conditions[conditionKey] = this ;
        this[privateKey] = recordType.collection(opts) ;
        this[privateKey].refresh() ; // get the initial data set.
      }
      return this[privateKey] ;
    }.property()
  },
  
  newRecord: function(attrs,dataSource) {
    if (!dataSource) dataSource = SC.Store ;
    var rec = this.create({ dataSource: dataSource }) ;
    rec.beginPropertyChanges() ;
    rec.set('newRecord',true) ;
    for(var key in attrs) {
      if (attrs.hasOwnProperty(key)) rec.set(key,attrs[key]) ;
    }
    rec.endPropertyChanges() ;
    SC.Store.addRecord(rec) ;
    return rec; 
  }
  
}) ;

SC.Record.Date = function(value,direction) {
  if (direction == 'out') {
    if (value instanceof Date) value = value.utcFormat() ;
    
  } else if (typeof(value) == "string") {
    var ret = Date.parseDate(value.replace(/\.\d+$/,'')) ;
    if (ret) value = ret ;
  }
  return value ;
}.typeConverter() ;

SC.Record.Number = function(value,direction) {
  if (direction == 'out') {
    if (typeof(value) == "number") value = value.toString() ;
  
  } else if (typeof(value) == "string") {
    var ret = (value.match('.')) ? parseFloat(value) : parseInt(value,0) ;
    if (ret) value = ret ;
  }
  return value ;
}.typeConverter() ;

SC.Record.Flag = function(value, direction) {
  if (direction == 'out') {
    return value = (value) ? 't' : 'f' ;
  } else if (typeof(value) == "string") {
    return !('false0'.match(value.toLowerCase())) ;
  } else return (value) ? true : false ;
}.typeConverter() ;



require('foundation/object') ;

SC.Store = SC.Object.create({
  
  updateRecords: function(dataHashes, dataSource, recordType, isLoaded) {
    
    this.set('updateRecordsInProgress',true) ;
    var store = this ; 
    if (!recordType) recordType = SC.Record ;
    this.beginPropertyChanges() ;

    dataHashes.each(function(data) {
      var rt = data.recordType || recordType; 
      if (data.recordType !== undefined) delete data.recordType;
      
      var pkValue = data[rt.primaryKey()] ;      
      var rec = store.getRecordFor(pkValue,rt,true) ;
      rec.dataSource = dataSource ;
      rec.updateAttributes(data, isLoaded, isLoaded) ;
      if (rec.needsAddToStore) store.addRecord(rec) ;
    });

    this.endPropertyChanges() ;
    this.set('updateRecordsInProgress',false) ;
  },


  refreshRecords: function(records) {},
  
  createRecords: function(recs) {
    recs.invoke('set','newRecord','false') ;
    this.commitRecords(recs); 
  },
  
  commitRecords: function(recs) { recs.invoke('set','isDirty','false'); },
  
  destroyRecords: function(recs) {
    var store = this ;
    recs.each(function(r) { 
      r.set('isDeleted',true); this.removeRecord(r);
    }) ;
  },
  
  
  addRecord: function(rec) {
    rec.needsAddToStore = false ;
    var guid = rec._storeKey();
    var records = this._records[guid] || [] ;
    records.push(rec) ;
    this._records[guid] = records ; 
    
    if (!this._quickCache) this._quickCache = {} ;
    var records = this._quickCache[guid] || {} ;
    records[rec.get(rec.primaryKey)] = rec ;
    this._quickCache[guid] = records ; 
    
    rec.addObserver('*',this._boundRecordObserver) ;
    this.recordDidChange(rec) ;
  },
  
  removeRecord: function(rec) {
    var guid = rec._storeKey();
    var records = this._records[guid] || [] ;
    records = records.without(rec) ;
    this._records[guid] = records ; 
    
    if (this._quickCache) {
      var records = this._quickCache[guid] || {} ;
      delete records[rec.primaryKey] ;
      this._quickCache[guid] = records ;
    }
    
    rec.removeObserver('*',this._boundRecordObserver) ;
    this.recordDidChange(rec) ; // this will remove from cols since destroyed.
  },

  findRecords: function() {
    var allConditions = $A(arguments) ;
    var recordType = allConditions.pop() ;
    var guid = recordType._storeKey() ;

    var records = this._records[guid] ;
    
    while(allConditions.length > 0) {
      var conditions = allConditions.pop() ;
      var ret = [] ; var loc = (records) ? records.length : 0;
      while(--loc >= 0) {
        var rec = records[loc] ;
        if ((rec._type == recordType) || (rec._type.coreRecordType == recordType)) {
          if (rec.matchConditions(conditions)) ret.push(rec) ;
        }
      }
      records = ret ;
    }
    
    return records ;
  },
  
  _getRecordFor: function(pkValue,recordType) {
    var guid = recordType._storeKey() ;
    var records = (this._quickCache) ? this._quickCache[guid] : null ;
    var ret = (records) ? records[pkValue] : null ;
    return ret ;
  },
  
  getRecordFor: function(pkValue,recordType,dontAutoaddRecord) {
    var ret = this._getRecordFor(pkValue,recordType) ;
    if (!ret) {
      var opts = {}; opts[recordType.primaryKey()] = pkValue;
      ret = recordType.create(opts) ;
      if (dontAutoaddRecord) {
        ret.needsAddToStore = true ;
      } else this.addRecord(ret) ;
    }
    return ret ;
  },
  
  
  addCollection: function(collection) {
    var guid = collection.recordType._storeKey() ;
    var collections = this._collections[guid] || [] ;
    collections.push(collection) ;
    this._collections[guid] = collections ;
  },
  
  removeCollection: function(collection) {
    var guid = collection.recordType._storeKey() ;
    var collections = this._collections[guid] || [] ;
    collections = collections.without(collection) ;
    this._collections[guid] = collections ;
  },
  
  listFor: function(opts) {
    var conditions = opts.conditions || {} ;
    var order = opts.order || ['guid'] ;
    var records = this.findRecords(conditions,opts.recordType) ;
    var count = records.length ;

    records = records.sort(function(a,b) { return a.compareTo(b,order); }) ;
    
    if (opts.limit && (opts.limit > 0)) {
      var start = (opts.offset) ? opts.offset : 0 ;
      var end = start + opts.limit ;
      records = records.slice(start,end) ;      
    }
    
    if (opts.callback) opts.callback(records,count) ; 
  },
  
  _records: {}, _changedRecords: null, _collections: {},
  
  recordDidChange: function(rec) {
    var guid = rec._storeKey() ;
    changed = this.get('_changedRecords') || {};
    records = changed[guid] || {} ;
    records[rec._guid] = rec ;

    changed[guid] = records ;    
    this.set('_changedRecords',changed) ;
  },
  
  _changedRecordsObserver: function() { 
    for(var guid in this._changedRecords) {
      var collections = this._collections[guid] ;
      if (collections && collections.length>0) {
        
        var records = [] ;
        for(var key in this._changedRecords[guid]) {
          records.push(this._changedRecords[guid][key]) ;
        }
        var cloc = collections.length ;
        while(--cloc >= 0) {
          
          var col = collections[cloc] ;
          col.beginPropertyChanges() ;
          try {
            var rloc = records.length ;
            while(--rloc >= 0) {
              var record = records[rloc] ;
              if (col.recordType == record._type) {
                col.recordDidChange(record) ;
              }
            }
          }
          catch (e) {
            console.log('EXCEPTION: While notifying collection') ;
          }
          col.endPropertyChanges() ;
          
        }
      }
    }
    
    this._changedRecords = {} ;
    
  }.observes('_changedRecords'),
  
  init: function() {
    arguments.callee.base.call(this) ;
    this._boundRecordObserver = this.recordDidChange.bind(this) ;
  }
    
}) ;


require('models/record') ;
require('models/store') ;

SC.Collection = SC.Object.extend({
  
  
  orderBy: ['guid'],

  offset: 0, limit: 0,
  
  conditions: {},
  
  records: function() {
    if (this._changedRecords) this._flushChangedRecords() ;
    return this._records ;
  }.property(),
  
  count: function(key, value) {
    if (value !== undefined) {
      this._count = value ;
    } else if (this._changedRecords) this._flushChangedRecords() ;
    return this._count || 0 ;
  }.property(),

  isDeleted: false, // RO
  
  dataSource: SC.Store,
  
  recordType: SC.Record, // NC

  isLoading: false, // RO
  
  isDirty: false, // RO
  

  refresh: function() {
    var recordType = this.get('recordType') || SC.Record ;
    var offset = (this._limit > 0) ? this._offset : 0 ;
    
    if (!this._boundRefreshFunc) {
      this._boundRefreshFunc = this._refreshDidComplete.bind(this) ;
    }    
    
    if (!this.dataSource) throw "collection does not have dataSource" ;
    this.beginPropertyChanges();
    if (!this.isLoading) this.set('isLoading',true) ;
    this._refreshing = true ;
    
    var order = this.get('orderBy') ;
    if (order && !(order instanceof Array)) order = [order] ;
    this.dataSource.listFor({
      recordType: recordType,
      offset: offset, 
      limit: this._limit,
      conditions: this.get('conditions'), 
      order: order,
      callback: this._boundRefreshFunc,
      cacheCode: this._cacheCode
    }) ;
    this.endPropertyChanges() ;
    return this; 
  },
  
  destroy: function() { SC.Store.removeCollection(this); return this; },
  
  newRecord: function(settings) {
    if (!settings) settings = {} ;
    settings.newRecord = true ;
    settings.dataSource = this.get('dataSource') ;
    var ret = this.recordType.create(settings);
    SC.Store.addRecord(ret) ; // this will add the record to the collection.
    return ret;
  },
  
  
  _offset: 0, _limit: 0, _records: null, _members: null, _store: null,
  
  init: function() {
    arguments.callee.base.call(this) ;
    SC.Store.addCollection(this) ; // get notified of changes.
    this._computeInteralOffsetAndLimit() ;
  },
  
  _refreshDidComplete: function(records,count,cacheCode) {
    if (cacheCode) this._cacheCode = cacheCode;

    if (records) {
      this.beginPropertyChanges() ;

      if (this.get('count') != count) this.set('count',count) ;

      this.propertyWillChange('records') ;
      records = this._store = records.slice() ;
      this._reslice() ;
      this.propertyDidChange('records') ;

      this.endPropertyChanges();
    }
    
    this._refreshing = false ; // only one refresh at a time.
  },

  recordDidChange: function(rec) {
    if (!rec && !rec._guid) return ; // probably an error, but recover anyway.
    if (!this._changedRecords) this._changedRecords = {} ;
    this._changedRecords[rec._guid] = rec ;
    this.propertyWillChange('records') ;
    this.propertyDidChange('records') ;
    this.propertyWillChange('count') ;
    this.propertyDidChange('count') ;
  },

  _flushChangedRecords: function() {
    if (!this._changedRecords) return ; // nothing to do.

    if (this.dataSource != SC.Store) throw "non-local data source is not supported"

    var current = this._store || [] ;
    var order = this.get('orderBy') || [this.recordType.primaryKey()] ;
    if (!(order instanceof Array)) order = [order] ;
    var conditions = this.get('conditions') ;

    var records = [] ;
    var changed = this._changedRecords ;
    for(var guid in changed) {
      if (!changed.hasOwnProperty(guid)) continue ;
      records.push(changed[guid]) ;
    }
    records = records.sort(function(a,b){ return a.compareTo(b,order); }) ;    
    this._changedRecords = null ;

    var loc = 0 ;
    while(loc < current.length)  {
      var working = current[loc] ;
      var compareToPrev, compareToNext ;
      
      if (changed[working._guid]) {

        var belongs = (!working.get('isDeleted')) && working.matchConditions(conditions) ;
        if (belongs) {
          
          if (loc>0) {
            belongs = (working.compareTo(current[loc-1],order) >= 0) ;
          }
          
          if (belongs && (loc+1 < current.length)) {
            belongs = (working.compareTo(current[loc+1],order) <= 0) ;
          }
        }
        if (!belongs) { current.splice(loc,1); continue; }
      }
      
      var goAgain = true ;
      while ((records.length > 0) && goAgain) {
        var rec = records[0] ;
        if ((rec != working) && !rec.get('isDeleted') && rec.matchConditions(conditions)) {
          if ((rec == working) || (rec.compareTo(working,order) <= 0)) {
            if (rec != working) current.splice(loc,0,rec) ; // insert only if not the same. 
            loc++ ;
          } else goAgain = false ;
        }
        if (goAgain) records.shift() ;
      }
      
      loc++ ;
    }

    while(records.length > 0) {
      var rec = records.shift() ;
      if (!rec.get('isDeleted') && rec.matchConditions(conditions)) {
        current.push(rec) ;
      }
    }
        
    
    this._store = current ;
    this._count = current.length ;
    this._reslice() ;
  },
  
  _oldRecordsDidChange: function() {
    var state = ((!rec.isDeleted) && rec.matchConditions(this.get('conditions'))) ? 'in' : 'out';
    var records = this._records ;
    
    if ((this._limit > 0) && (state == "in") && records && (records.length > 0)) {
      var order = this.get('orderBy') ;
      if (rec.compareTo(records[0],order) < 0) {
        state = "before" ;
      } else if (rec.compareTo(records[records.length-1],order) > 0) {
        state = "after" ;
      }
    }
    
    var belongs = records.include(rec) ;
    var reslice = false ; var refresh = false ;
    
    switch(state) {
      case 'before':
        if (belongs) { records = records.without(rec) ;  reslice = true; }
        refresh = true ;
        break ;

      case 'after':
      case 'out':
        if (belongs) {
          records = records.without(rec) ;
          reslice = refresh = true ;
        }
        break ;
        
      case 'in':
        if (!belongs) {
          records = records.slice();
          records.push(rec) ;
        } 
        records = records.sort(function(a,b){ return a.compareTo(b,order); });
        if (!records.isEqual(this._records)) reslice = refresh = true ;
        break;
    }
    
    if (reslice) {
      this._records = records ;
      if (this._limit > 0) {
        var start = this.get('offset') - this._offset ;
        var end = start + this.get('limit') ;
        records = records.slice(start,end) ;
      }
      this.set('records',records) ;
    }
    
  },
  
  propertyObserver: function(observing,target,key,value) {
    if (target != this) return ;

    var needsRefresh = false ; 
    var nv ;
    value = this.get(key) ; 
    switch(key) {
      case 'offset':
      case 'limit':
        var oldOffset = this._offset ;
        var oldLimit = this._limit ;
        this._computeInteralOffsetAndLimit() ;
        if ((this._offset == oldOffset) && (this._limit == oldLimit)) {
          this.propertyWillChange('records') ;
          this._reslice();
          this.propertyDidChange('records') ;
        } else needsRefresh = true ;
        break ;
        
      case 'conditions':
      case 'orderBy':
        needsRefresh = true ;
        break ;
        
      default:
        break ;
    }

    if (needsRefresh && !this._refreshing) {
      this._refreshing = true ;
      this._cacheCode = null ;
      this.set('isLoading',true) ; 
      setTimeout(this.refresh.bind(this),1);
    }
  },
  
  _computeInteralOffsetAndLimit: function() {
    if (this.dataSource != SC.Store) {
      var v ;
      this._offset = ((v=this.get('offset')) > this.MARGIN) ? (v-this.MARGIN) : 0 ;
      this._limit = ((v=this.get('limit')) > 0) ? (v + this.MARGIN) : 0 ; 
    } else this._offset = this._limit = 0 ;
  },
  
  _reslice: function() {
    var offset = this.get('offset') ; 
    var limit = this.get('limit') ;
    if ((offset > 0) || (limit > 0)) {
      var start = offset - this._offset ;
      var end = start + ((limit <= 0) ? (this._store || []).length : limit) ;
      this._records = this._store.slice(start,end) ;
    } else this._records = this._store ;
  },
  
  MARGIN: 10
  
}) ;


Object.extend(String.English,{
  'Invalid.CreditCard(%@)': '%@ is not a valid credit card number',
  'Invalid.Email(%@)': '%@ is not a valid email address',
  'Invalid.NotEmpty(%@)': '%@ must not be empty',
  'Invalid.Password': "Your passwords do not match.  Please try typing them again.",
  'Invalid.General(%@)': '%@ is invalid.  Please try again.'
}) ;

SC.Validator = SC.Object.extend({

  
  fieldValueForObject: function(object, form, field) { return object; },
  
  objectForFieldValue: function(value, form, field) { return value; },
  
  validate: function(form, field) { return true; },

  validateError: function(form, field) { 
    return "Invalid.General(%@)".loc(field.get('fieldValue')) ; 
  },

  validateChange: function(form, field, oldValue) { 
    return (this.validate(form,field)) ? SC.Validator.OK : this.validateError(form, field);
  },

  validateSubmit: function(form, field) { 
    return (this.validate(form,field)) ? SC.Validator.OK : this.validateError(form, field);
  },

  validatePartial: function(form, field) { 
    if (!field.get('isValid')) {
      return (this.validate(form,field)) ? SC.Validator.OK : this.validateError(form, field);
    } else return SC.Validator.NO_CHANGE ;
  },
  
  validateKeypress: function(form, field,charStr) { return true; },

  
  _attachTo: function(form,field) {
    this._form = form ;
    this._field = field ;
    var that = this ;
    ['focus','change','blur','keypress'].each(function(key) {
      var f = that['_'+key].bindAsEventListener(that) ;
      Event.observe(field,key,f) ;
    }) ;
    
    if (!this._boundPartial) this._boundPartial = this._partial.bind(this) ;
    if (field.addObserver) field.addObserver('fieldValue',this._boundPartial);
    return this ;
  },
  
  detach: function() {
    var that = this ;
    ['focus','change','blur', 'keypress'].each(function(key) {
      var f = that['_'+key].bindAsEventListener(that) ;
      Event.stopObserving(that._field,key,f) ;
    }) ;
    if (this._field.removeObserver) {
      this._field.removeObserver('fieldValue',this._boundPartial) ;
    }
    return this ;
  },
  
  _focus: function(ev) { 
    this._oldValue = this._field.get('fieldValue'); 
  },
  
  _blur: function(ev) {
    if (this._oldValue != this._field.get('fieldValue')) this._change(ev) ;
    this._oldValue = null ;  
  },
  
  _change: function(ev)  {
    var err = this.validateChange(this._form,this._field,this._oldValue) ;
    this._oldValue = null ;
    if (err == SC.Validator.NO_CHANGE) return ; // nothing to do.
    if (err == SC.Validator.OK) err = null ;
    this._form.setErrorFor(this._field,err) ; // also clears error.
  },
  
  _keypress: function(ev) {
    var str = Event.getCharString(ev) ;
    var allow = this.validateKeypress(this._form,this._field,str) ;
    return allow ;
  },
  
  _partial: function() {
    var err = this.validatePartial(this._form,this._field) ;
    if (err == SC.Validator.NO_CHANGE) return ; // nothing to do
    if (err == SC.Validator.OK) err = null ;
    if (this._field.set) this._field.set('isValid', err == null)
    this._form.setErrorFor(this._field,err) ;
  },
  
  _validate: function() {
    var err = this.validateSubmit(this._form,this._field) ;
    if (err == SC.Validator.NO_CHANGE) return true ; // nothing to do.
    if (err == SC.Validator.OK) err = null ;
    this._form.setErrorFor(this._field,err) ;
    return (err == null) ; // true = ok to submit.
  }
  
}) ;

SC.Validator.mixin({

  OK: '.OK', NO_CHANGE: '.NO_CHANGE',  

  attachTo: function(form,field, validatorKey) {
    var name = null ;
    var m = validatorKey.match(/^(.+)\[(.*)\]/) ;
    if (m) {
      validatorKey = m[1] ; name = m[2]; 
    }
    validatorKey = ('-' + validatorKey).camelize() ;
    var validatorClass = SC.Validator[validatorKey] ;
    if (validatorClass == null) {
    } else {
      if (!form._validatorHash) form._validatorHash = {} ;
      var validator = (name) ? form._validatorHash[name] : null ;
      if (!validator) validator = validatorClass.create() ;
      if (name) form._validatorHash[name] = validator ;
      validator._attachTo(form,field) ;
    }
    return validator ;
  },
  
  fieldValueForObject: function(object, form, field) {
    return this.prototype.fieldValueForObject(object,form,field) ;
  },
  
  objectForFieldValue: function(value, form, field) {
    return this.prototype.objectForFieldValue(value,form,field) ;
  }
  
})

require('validators/validator') ;

SC.Validator.CreditCard = SC.Validator.extend({
  
  fieldValueForObject: function(object, form, field) {
    if (typeof(object) == "string" && object.length == 16) {
      object = [object.slice(0,4),object.slice(4,8),object.slice(8,12),object.slice(12,16)].join(' ') ;
    }
    return object ;
  },
  
  objectForFieldValue: function(value, form, field) {
    return value.replace(/^\s+|\s+$/g,'') ;
  },
  
  validate: function(form, field) { 
    return this.checkNumber(field.get('fieldValue')) ; 
  },
  
  validateError: function(form, field) {
    return "Invalid.CreditCard(%@)".loc(field.get('label'));
  },
  
  checkNumber: function(ccNumb) {
    var valid = "0123456789"  // Valid digits in a credit card number
    var len = ccNumb.length;  // The length of the submitted cc number
    var iCCN = parseInt(ccNumb);  // integer of ccNumb
    var sCCN = ccNumb.toString();  // string of ccNumb
    sCCN = sCCN.replace (/^\s+|\s+$/g,'');  // strip spaces
    var iTotal = 0;  // integer total set at zero
    var bNum = true;  // by default assume it is a number
    var bResult = false;  // by default assume it is NOT a valid cc
    var temp;  // temp variable for parsing string
    var calc;  // used for calculation of each digit

    for (var j=0; j<len; j++) {
      temp = "" + sCCN.substring(j, j+1);
      if (valid.indexOf(temp) == "-1"){bNum = false;}
    }

    if(!bNum) bResult = false;

    if((len == 0)&&(bResult)){  // nothing, field is blank AND passed above # check
      bResult = false;
    } else{  // ccNumb is a number and the proper length - let's see if it is a valid card number
      if(len >= 15){  // 15 or 16 for Amex or V/MC
        for(var i=len;i>0;i--){  // LOOP throught the digits of the card
          calc = parseInt(iCCN) % 10;  // right most digit
          calc = parseInt(calc);  // assure it is an integer
          iTotal += calc;  // running total of the card number as we loop - Do Nothing to first digit
          i--;  // decrement the count - move to the next digit in the card
          iCCN = iCCN / 10;                               // subtracts right most digit from ccNumb
          calc = parseInt(iCCN) % 10 ;    // NEXT right most digit
          calc = calc *2;                                 // multiply the digit by two
          switch(calc){
            case 10: calc = 1; break;       //5*2=10 & 1+0 = 1
            case 12: calc = 3; break;       //6*2=12 & 1+2 = 3
            case 14: calc = 5; break;       //7*2=14 & 1+4 = 5
            case 16: calc = 7; break;       //8*2=16 & 1+6 = 7
            case 18: calc = 9; break;       //9*2=18 & 1+8 = 9
            default: calc = calc;           //4*2= 8 &   8 = 8  -same for all lower numbers
          }                                               
        iCCN = iCCN / 10;  // subtracts right most digit from ccNum
        iTotal += calc;  // running total of the card number as we loop
      }  // END OF LOOP
      if ((iTotal%10)==0){  // check to see if the sum Mod 10 is zero
        bResult = true;  // This IS (or could be) a valid credit card number.
      } else {
        bResult = false;  // This could NOT be a valid credit card number
        }
      }
    }
    return bResult; // Return the results
  }
    
}) ;


require('validators/validator') ;

SC.Validator.Date = SC.Validator.extend({

  format: 'NNN d, yyyy h:mm:ss a',
  naturalLanguage: true,
  
  fieldValueForObject: function(object, form, field) {
    var date ;
    if (typeof(object) == "number") {
      date = new Date(object) ;
    } else if (object instanceof Date) { date = object; }
      
    if (date) object = date.format(this.get('format'),this.get('naturalLanguage')) ;
    
    return object ;
  },

  objectForFieldValue: function(value, form, field) {
    if (value) {
      var date = Date.parseDate(value) ;
      value = (date) ? date.getTime() : null ;
    }
    return value ;
  }
    
}) ;


require('validators/validator') ;
SC.Validator.Email = SC.Validator.extend({
  
  validate: function(form, field) { 
    return field.get('fieldValue').match(/.+@.+\...+/) ; 
  },
  
  validateError: function(form, field) {
    return "Invalid.Email(%@)".loc(field.get('label')) ;
  }  
    
}) ;

SC.Validator.EmailOrEmpty = SC.Validator.Email.extend({
  validate: function(form, field) {
    var value = field.get('fieldValue') ; 
    return (value && value.length > 0) ? value.match(/.+@.+\...+/) : true ;
  }
}) ;


require('validators/validator') ;
SC.Validator.NotEmpty = SC.Validator.extend({
  
  validate: function(form, field) {
    var value = field.get('fieldValue'); 
    var ret = !!(value || value.length > 0) ; 
    return ret ;
  },
  
  validateError: function(form, field) {
    var label = field.get('label') || 'Field' ;
    return "Invalid.NotEmpty(%@)".loc(label.capitalize()) ;
  }
    
}) ;


require('validators/validator') ;

SC.Validator.Number = SC.Validator.extend({

  places: 0, // 0 to force int. otherwise fixed.
  naturalLanguage: true,
  
  fieldValueForObject: function(object, form, field) {
    switch($type(object)) {
      case T_NUMBER:
        object = object.toFixed(this.get('places')) ;
        break ;
      case T_NULL:
      case T_UNDEFINED:
        object = '0';
        break ;
    }
    return object ;
  },

  objectForFieldValue: function(value, form, field) {
    switch($type(object)) {
      case T_STRING:
        value = (this.get('places') > 0) ? parseFloat(value) : parseInt(value) ;
        break ;
      case T_NULL:
      case T_UNDEFINED:
        value = 0 ;
        break ;
    }
    return value ;
  }
    
}) ;


require('validators/validator') ;
SC.Validator.Password = SC.Validator.extend({

  _attachTo: function(form,field) {
    argments.callee.base.call(this,form,field) ;
    if (!this.fields) this.fields = [] ;
    this.fields.push(field) ;
  },

  validate: function(force) {
    if (!this.fields || this.fields.length == 0) return true ;
    
    var empty = false ;
    var notEmpty = false ;
    var ret = true ;
    var value = this.fields[0].get('fieldValue') ;
    this.fields.each(function(field) {
      var curValue = field.get('fieldValue') ;
      if (curValue != value) ret= false ;
      if (!curValue || curValue.length == 0) empty = true ;
      if (curValue && curValue.length > 0) notEmpty = true ;
    }) ;

    if (force) {
      return (notEmpty == false) ? false : ret ;
    } else {
      return (empty == true) ? true : ret ;
    }
  },
  
  updateFields: function(form,valid) {
    if (!this.fields || this.fields.length == 0) return true ;
    var err = "Invalid.Password".loc();
    var topField = this._field ;
    this.fields.each(function(f) {
      var msg = (valid) ? null : ((f == topField) ? err : '') ;
      form.setErrorFor(f,msg) ;
    }) ;
    return (valid) ? SC.Validator.OK : err ;
  },
  
  validateChange: function(form, field, oldValue) { 
    return this.updateFields(form, this.validate(false)) ;
  },

  validateSubmit: function(form, field) { 
    return this.updateFields(form, this.validate(true)) ;
  },

  validatePartial: function(form, field) {
    var isInvalid = !this._field.get('isValid') ;
    if (isInvalid) {
      return this.updateFields(form, this.validate(false)) ;
    } else return SC.Validator.NO_CHANGE ;
  }
    
}) ;


require('views/view') ;

SC.LabelView = SC.View.extend({

  escapeHTML: true,

  localize: false,
  
  property: function(key, value) {
    if ((value !== undefined) && (value != this._property)) {
      if (this._content) {
        var func = this._boundObserver() ;
        if (this._property) this._content.removeObserver(this._property,func);
        this._property = value ;
        if (this._property) this._content.addObserver(this._property,func) ;
      }
    }
    return this._property ;
  }.property(),
  
  formatter: null,
  
  content: function(key,value) {
    if ((value !== undefined) && (this._content != value)) {
      var prop = this.get('property') ;
      var func = this._boundObserver() ;
      if (prop && this._content) this._content.removeObserver(prop, func) ;
      this._content = value ;
      if (prop && this._content) this._content.addObserver(prop, func) ;
      this._updateValue() ;      
    } 
    return this._content ;
  }.property(),

  _updateValue: function() {
    var value = this._content ;
    var prop = this.get('property') ;
    if (prop && value) value = value.get(prop) ;

    var formatter = this.get('formatter') ;
    if (formatter) {
      var formattedValue = (SC.typeOf(formatter) == "function") ? formatter(value) : formatter.fieldValueForObject(value) ;
      if (formattedValue) value = formattedValue ;
    }

    if ($type(value) == 'number') value = value.toString() ; // handle 0
    
    if (value && this.get('localize')) value = value.loc() ;
    if (value && this.get('escapeHTML') && value.escapeHTML) value = value.escapeHTML() ;
    
    this.set('asHTML',value || '') ;

  },
  
  _boundObserver: function() {
    if (!this._observer) this._observer = this._updateValue.bind(this) ;
    return this._observer ;
  },
  
  init: function()
      {
        arguments.callee.base.call(this) ;
    
        if(this.get("localize"))
        {
         var inner = this.get("asHTML");
         if( inner !== "")
         {
           this.set("content", inner); 
         }
       }
     },

 contentBindingDefault: SC.Binding.Single

}) ;


require('views/view') ;
require('views/label') ;

SC.ButtonView = SC.View.extend({
  
  
  isEnabled: true,
  isEnabledBindingDefault: SC.Binding.OneWayFlag,
  
  isSelected: false,
  isSelectedBindingDefault: SC.Binding.OneWayFlag,

  localize: false,
  
  labelText: function(key, value) {
    if (value !== undefined) {
      if (this._labelText != value) {
        var text = this._labelText = value ;
        var el = this.$sel(this.labelSelector) ;

        if (this.get('localize')) text = text.loc() ;
        if (el) Element.update(el, text) ;
      }
      
      if (!this._labelText) {
        var el = this.$sel(this.labelSelector) ;
        this._labelText = (el) ? el.innerHTML : '' ;
      }
      return this._labelText ;
    }
    
    return this._labelText ;
  }.property(),
  
  labelSelector: '.label',
  
  action: function(evt) {},
  
  init: function() {
    arguments.callee.base.call(this) ;
    this._updateClassForState() ;
    
    var el ;
    if (this.get('localize') && (el = this.$sel(this.labelSelector))) {
      this._labelText = el.innerHTML.strip() ;
      Element.update(el, this._labelText.loc()) ;
    }
  },
  
  propertyObserver: function(observing,target,key,value) {
    if ((target == this) && (['isEnabled','isSelected'].include(key))) {
      this._updateClassForState() ;
    }
  },
  
  _updateClassForState: function() {
    var enabled = this.get('isEnabled') ;
    var tagName = this.rootElement.tagName.toLowerCase() ;
    if (enabled) {
      this.removeClassName('disabled') ;
      if (tagName == 'button') this.disabled = false ;
    } else {
      this.addClassName('disabled') ;
      if (tagName == 'button') this.disabled = true ;
    }

    (this.get('isSelected')) ? this.addClassName('sel') : this.removeClassName('sel') ;
  },
    
  click: function(evt) {
    if (this.get('isEnabled')) this.action(evt) ;
  },
  
  mouseEntered: function(evt) { this.addClassName('hover');    },
  mouseExited: function(evt)  { this.removeClassName('hover'); }

}) ;


require('views/view') ;

SC.FieldView = SC.View.extend({
  
  value: function(key,value) {
    if (value !== undefined) this.setFieldValue(value) ;
    return this.getFieldValue() ;
  }.property('fieldValue'),
    
  fieldValue: function(key,value) {
    if (value !== undefined) this.setFieldValue(value) ;
    return this.getFieldValue() ;
  }.property('value'),
  
  isValid: true, 
  isEnabled: true,
  
  setFieldValue: function(newValue) {
    if (this.rootElement.value != newValue) this.rootElement.value = newValue;
  },
  
  getFieldValue: function() {
    return this.rootElement.value;
  },
  
  init: function() {
    arguments.callee.base.call(this) ;
    if (this.rootElement) this.setFieldValue(this.rootElement.value) ;
    this.label = this.getAttribute('label') || this.getAttribute('name') ;
    this.configureObserverMethods(SC.FieldView.ObserverMethods) ;
  },
  
  _validObserver: function() {
    this.setClassName('invalid',!this.get('isValid')) ;
  }.observes('isValid'),
  
  _enabledObserver: function(target, key, isEnabled) {
    if (isEnabled) {
      Form.Element.enable(this.rootElement) ;
    } else {
      Form.Element.disable(this.rootElement) ;
    }
  }.observes('isEnabled')
  
}) ;

SC.FieldView.ObserverMethods = {
  "didKeyDown": "keydown",
  "didKeyUp": "keyup",
  "didKeyPress": "keypress",
  "didFocus": "focus",
  "didBlur": "blur",
  "didChange": "change",
  "didSubmit": "submit" // forms only.
}




require('views/field') ;

SC.CheckboxFieldView = SC.FieldView.extend({
  
  emptyElement: '<input type="checkbox" value="1" />',
  
  setFieldValue: function(value) {
    this.rootElement.checked = !!value;
  },
  
  getFieldValue: function() {
    return this.rootElement.checked;
  },
  
  fieldValueBindingDefault: SC.Binding.Flag,
  didChange: function() { this.propertyDidChange('value') ; }
  
}) ;


require('views/view') ;
require('views/label') ;

SC.CollectionView = SC.View.extend({
  
  
  content: [],
  contentBindingDefault: SC.Binding.MultipleNotEmpty,
  
  selection: [],
  selectionBindingDefault: SC.Binding.Multiple,
  
  isSelectable: true,
  isSelectableBindingDefault: SC.Binding.Flag,
  
  isEnabled: true,
  isEnabledBindingDefault: SC.Binding.Flag,

  useToggleSelection: false,
  
  flushUnusedViews: true,

  actOnSelect: false,  

  groupBy: null,
  
  exampleView: SC.View,

  exampleGroupView: SC.View.extend({
    emptyElement: '<div><h1></h1><div class="well"></div></div>',
    outlets: ['labelView','itemView'],
    labelView: SC.LabelView.outletFor('h1?'),
    itemView: SC.View.outletFor('.well?')
  }),
  
  action: function(ev) { },
  
  isDirty: true,
  
  maxRenderTime: 0,
  
  
  updateChildren: function() {
    
    var el = this.containerElement || this.rootElement ;
    
    if (this._firstUpdate) { 
      el.innerHTML ='';
      this._firstUpdate = false ; 
    }    

    this.cacheFrame() ;

    if (!this._viewsForContent) this._viewsForContent = {} ;

    var groupBy = this.get('groupBy') ;
    var content = this.get('content') || [] ;
    
    var cachedParent = null ;
    var cachedSibling = null ;
    if (content.length != this.childNodes.length) {
      cachedParent = el.parentNode ;
      cachedSibling = el.nextSibling ;
      cachedParent.removeChild(el) ;
    }
    
    if (groupBy) {
      var loc = 0 ;
      var group = this.firstChild ;
      while(group || (loc < content.length)) {
        var groupValue = (loc < content.length) ? content[loc].get(groupBy) : null ;
        
        if (loc >= content.length) {
          if (group) {
            loc = this.updateChildrenInGroup(group.itemView, content, loc, groupBy, null);
            var prev = group.previousSibling ;
            this.removeChild(group) ;
            group = prev ;
          }
          
        } else if (!group || (group.get('groupValue') != groupValue)) {
          
          var newGroup = this.exampleGroupView.viewFor(null) ; 
          newGroup.set('groupValue',groupValue) ;
          
          if (newGroup.labelView) newGroup.labelView.set('content',groupValue);

          loc = this.updateChildrenInGroup(newGroup.itemView,content,loc,
                                           groupBy, groupValue) ;

          this.insertBefore(newGroup,group) ; 
          group = newGroup ;
          
        } else {
          loc = this.updateChildrenInGroup(group.itemView,content,loc,
                                            groupBy, groupValue) ;
        }
        
        group = (group) ? group.nextSibling : this.firstChild ;
      }
      
    } else {
      this.updateChildrenInGroup(this, content, 0, null, null) ;
    }
    
    if (cachedParent) cachedParent.insertBefore(el,cachedSibling) ;
    
    this.updateSelectionStates() ;
    this.flushFrameCache() ;
    this.set('isDirty',false); 
  },

  updateChildrenInGroup: function(parent,content,loc,groupBy,groupValue) {
  
    var child = parent.firstChild ;
    var inGroup = true ;

    this.updateComputedViewHeight(parent) ;

    var expired = false ;
    var canExpire = !groupBy && loc == 0 ; 
    if (canExpire) {
      loc = this._lastRenderLoc ;
      child = this._lastRenderChild ;
      this._resetRenderClock();
    } ;

    var firstChild = null ;
    
    while(child || (inGroup && (loc < content.length) && !expired)) {
      
      var cur = (inGroup && (loc < content.length)) ? content[loc] : null ;
      
      if (cur && groupBy && (cur.get(groupBy) != groupValue)) {
        inGroup = false; cur = null ;
      }
      
      if (cur == null) {
        if (child) {
          if (this.flushUnusedViews) {
            var viewContent = child.get('content') ;
            if (viewContent) delete this._viewsForContent[viewContent._guid];
            child.set('content',null) ;
          }
          var prev = child.previousSibling ;
          parent.removeChild(child) ;
          child = prev ;
        }

      } else if (!child || (child.get('content') != cur)) {

        var newChild = this._viewsForContent[cur._guid] ;
        if (!newChild) {
          newChild = this.exampleView.viewFor(null) ;
          newChild._isChildView = true ;
          newChild.set('content',cur) ;
          this._viewsForContent[cur._guid] = newChild ;
        }
        
        parent.insertBefore(newChild,child) ;
        child = newChild ;
      }
      
      if (!firstChild) firstChild = child ;
      child = (child) ? child.nextSibling : ((inGroup) ? parent.firstChild : null) ;
      
      if (cur) loc++ ;
      
      expired = this._renderExpired() ;
    }

    if (expired && (loc < content.length)) {
      this._lastRenderLoc = loc ;
      this._lastRenderChild = child ;
      setTimeout(this.updateChildren.bind(this),1) ; // do more later.
    } else this._resetExpiredRender() ;
    
    this.layoutChildViewsFor(parent, firstChild) ;
    return loc ;
  },
  
  rebuildChildren: function() {
    this.clear() ;
    this._viewsForContent = {} ;
    this._resetExpiredRender() ;
    this.updateChildren() ;    
  },
  
  updateSelectionStates: function() {
    var selection = this.get('selection') || [];
    if (this.childNodes) this.childNodes.each(function(child) {
      var content = (child.get) ? child.get('content') : null ;
      if (child.set) child.set('isSelected', (content) ? selection.include(content) : false) ;
    }) ;
  },

  layoutChildViewsFor: function(parentView, startingView) { return false; },
  
  resizeChildrenWithOldSize: function(oldSize) {
    if (this.layoutChildViewsFor(this, null) === false) {
      arguments.callee.base.apply(this,arguments) ;
    } else {
      this.updateComputedViewHeight(this) ;
    }
  },
  
  _firstUpdate: true,
  
  _lastRenderLoc: 0,
  _renderStart: null,
  _resetRenderClock: function() { this._renderStart = new Date().getTime(); },

  _resetExpiredRender: function() { 
    this._lastRenderLoc = 0; this._lastRenderChild = null;
  },
  
  _renderExpired: function() {
    var max = this.maxRenderTime ;
    if ((this._renderStart == null) || (max == 0)) return false ;
    return ((new Date().getTime()) - this._renderStart) > max ;
  },

  
  
  itemRangeInFrame: function(groupView, frame) { return null; },
  
  computedViewHeight: function(groupView) { return -1; },
  
  updateComputedViewHeight: function(groupView) {
    var height = this.computedViewHeight(groupView) ;
    if (height <= 0) {
      if (groupView._heightView) {
        groupView.rootElement.removeChild(this._heightView) ;
        groupView._heightView = null ;
      }
    } else {
      if (!groupView._heightView) {
        groupView._heightView = document.createElement('div') ;
        groupView.rootElement.appendChild(groupView._heightView) ;
        Element.setStyle(groupView._heightView,{
          position: 'absolute', left: '0px', display: 'block',
          width: '1px', height: '1px'
        }) ;
      }
      
      if (height != groupView._lastComputedHeight) {
        Element.setStyle(groupView._heightView,{ top: height + 'px' }) ;
        groupView._lastComputedHeight = height ;
      }
    }
  },
  

  selectItems: function(items, extendSelection) {
    var base = (extendSelection) ? this.get('selection') : [] ;
    var sel = [items].concat(base).flatten().uniq() ;
    this.set('selection',sel) ;  
  },
  
  deselectItems: function(items) {
    items = [items].flatten() ;
    var base = this.get('selection') || [] ; 
    var sel = base.map(function(i) { return (items.include(i)) ? null : i; });
    sel = sel.compact() ;
    this.set('selection',sel) ;
  },
  
  
  itemForEvent: function(evt) {
    var el = Event.element(evt) ;
    while(el && (el != this.rootElement) && (!el._configured || (this._viewsForContent[el._configured] == null))) {
      el = el.parentNode ;
    }
    
    if (el == this.rootElement) el = null ;
    if (el) el = this._viewsForContent[el._configured] ; // get view instance.
    if (el && el.hitTest && !el.hitTest(evt)) el = null ;
    return el ;
  },

  didMouseDown: function(ev) { 
    
    if (this.useToggleSelection) return ;
    
    var view = this.itemForEvent(ev) ;
    var extendSelection = ev.shiftKey || ev.ctrlKey || ev.altKey || ev.metaKey;
    var selection = this.get('selection') || [] ;
    var content = (view) ? view.get('content') : null ;
    var isSelected = selection.include(content) ;
    
    if (view) {
      if (view.checkboxView && (Event.element(ev) == el.checkboxView.rootElement)) {
        extendSelection = true ;
      } 

      this._mouseDownAt = new Date().getTime() ;
      if (extendSelection && isSelected) {
        this._shouldDeselect = content ;
      } else {
        if (ev.shiftKey && selection.length > 0) {
          var sel = [] ;
          var cur = view ;
          var selectionFound = false ;
          var curContent ;
            
          while(cur && !selectionFound) {
            if (cur.get && (curContent = cur.get('content'))) {
              sel.push(curContent) ;
              selectionFound = selection.include(curContent) ;
            }
            cur = cur.previousSibling ;
          }

          if (!selectionFound) {
            sel = [] ; cur = view ;
            while(cur && !selectionFound) {
              if (cur.get && (curContent = cur.get('content'))) {
                sel.push(curContent) ;
                selectionFound = selection.include(curContent) ;
              }
              cur = cur.nextSibling ;
            }
          }

          if (selectionFound) content = sel ;
        }

        if (!extendSelection && isSelected) {
          this._shouldReselect = content ;
        } else {
          this.selectItems(content, extendSelection) ;
        }
      }
    } else this.selectItems([], false) ;
    return true ;
  },

  didMouseUp: function(ev) { 

    var canAct = this.get('actOnSelect') ;
    var view = this.itemForEvent(ev) ;
    
    if (this.useToggleSelection) {
      if (!view) return ; // do nothing when clicked outside of elements
      
      var selection = this.get('selection') || [] ;
      var content = (view) ? view.get('content') : null ;
      var isSelected = selection.include(content) ;
      if (isSelected) {
        this.deselectItems([content]) ;
      } else this.selectItems([content],true) ;
      
    } else {
      if (this._shouldDeselect) this.deselectItems(this._shouldDeselect) ;
      if (this._shouldReselect) this.selectItems(this._shouldReselect,false) ;

      if (this._refreshSelection) {
      }
      this._mouseDownAt = this._shouldDeselect = this._shouldReselect = this._refreshSelection = false ;
    }

    if (canAct) this.action(ev, view) ;
  },

  mouseDidMove: function(ev) {
    var view = this.itemForEvent(ev) ;
    if(this._lastHoveredItem && ((view === null) || (view != this._lastHoveredItem)) && this._lastHoveredItem.didMouseOut) {
      this._lastHoveredItem.didMouseOut(ev); 
    }
    this._lastHoveredItem = view ;
    if (view && view.didMouseOver) view.didMouseOver(ev) ;
  },

  didMouseOut: function(ev) {
    var view = this._lastHoveredItem ;
    this._lastHoveredItem = null ;
    if (view && view.didMouseOut) view.didMouseOut(ev) ;
  },
  
  didDoubleClick: function(ev) {
    var view = this.itemForEvent(ev) ;
    if (view) this.action(view, el) ;
  },
  
  _viewsForContent: null,
  _content: [], // cached for changes.
  propertyObserver: function(observing,target,key,value) {
    if (target == this) {
      var el = this ;
      
      if (key == 'content') {
        
        var isEqual = ((value && this._content) && (value.length == 0) && (this._content.length == 0)) || SC.isEqual(value,this._content) ;
        this._content = value ;

        if (!isEqual && !this._updating) {
          this._updating = true ;
          this.set('isDirty',true) ;
          this._resetExpiredRender();
          this.updateChildren() ;
          this._updating = false ;
        }
        
      } else if (key == 'selection') {
        if (!this._updatingSel) {
          this._updatingSel = this.invokeLater('_updateSelectionState',1);
        }
      }  
    }
  },
  
  _updateSelectionState: function() {
    try {
      this.updateSelectionStates() ;
    } catch(e) {
      console.log('exception while updating selection states in %@: %@'.format(this,e)) ;
    }
    this._updatingSel = null ;
  }
  
}) ;


require('views/button') ;

SC.FilterButtonView = SC.ButtonView.extend({
  
  filterValue: null, // relay to this property.

  filterOn: null, // when this filter toggle on, it will go to this.
  filterOff: null, // when this filter toggles off, it goes to this.
  
  action: function() {
    var val = this.get('filterValue') ;
    val = (val == this.get('filterOn')) ? this.get('filterOff') : this.get('filterOn') ;
    this.set('filterValue',val) ;  
  },
  
  filterValueObserver: function() {
    var sel = this.get('filterValue') == this.get('filterOn') ;
    if (sel != this.get('isSelected')) this.set('isSelected',sel) ;
  }.observes('filterValue')
  
});                                                                              

require('views/field') ;

SC.TextFieldView = SC.FieldView.extend({
  
  emptyElement: '<input type="text" value="" />',
  
  hintValue: null,
  
  isFocused: false,
  isHintShowing: false,
  hintClassName: 'show-hint',
  realTimeValidation: true,
  
  init: function() {
    arguments.callee.base.call(this) ;
    this._value = this.rootElement.value ;
    this.hintValue = this.getAttribute('hint') ;
    this._updateFieldHint(true) ;
    this._wasFocused = false ;
  },
  
  getFieldValue: function() {
    return this._value ;
  },
  
  setFieldValue: function(value) {
    this._value = value ;
    if (value != this.rootElement.value) {
      this.rootElement.value = value ;
      this._updateFieldHint(true) ;
    }
  },
  
  didKeyPress: function() {
    if (!this._boundKeypress) this._boundKeypress = this._keypress.bind(this);
    setTimeout(this._boundKeypress,1) ;
  },
  
  _keypress: function() { 
    if (this.get('isFocused') && this.get("realTimeValidation")) {
      this._value = this.rootElement.value ;
      this.propertyDidChange('value') ;
    }
  },
  
  didFocus: function() { this.set('isFocused',true); },
  didBlur: function() { this.set('isFocused',false); },
  
  _focusObserver: function(target,key,isFocused) {
    
    if (isFocused == this._wasFocused) return ;
    this._wasFocused = isFocused ;
    
    if (!isFocused) this._value = this.rootElement.value;
    this._updateFieldHint(); 
  }.observes('isFocused'),
  
  _updateFieldHint: function(force) {
    var hintShowing = this.get('isHintShowing') ;
    var fieldValue = '' + this.get('value') ; // force to a string
    var hintValue = this.get('hintValue') ;
    var isFocused = this.get('isFocused') ;
    
    var showHint = !isFocused && (fieldValue == '') && (hintValue != null)  ;
    
    if (showHint) {
      this.rootElement.value = hintValue.loc() ;
      this.addClassName(this.hintClassName)
    } else {
      this.rootElement.value = fieldValue ;
      this.removeClassName(this.hintClassName) ;
    }
    
    this.set('isHintShowing',showHint) ;
  } 
  
}) ;


require('views/view') ;
require('views/button') ;
require('views/text_field') ;

SC.FormView = SC.View.extend({


  formValues: function(key,value) {
    if (value !== undefined) {
      this._formValues = value ;
      if (!this._commiting) this.setCurrentFormValues(this._formValues) ;
    }
    return this._formValues ;
  }.property(),
  
  content: function(key,value) {
    if (value !== undefined) {
      
      var commitPrompt = this.get('commitPrompt') ;
      if (this.get('isDirty') && commitPrompt && confirm(commitPrompt)) { 
        this.commitForm() ;
      }
      
      if (!this._boundContentPropertyFunc) {
        this._boundContentPropertyFunc = this._contentPropertyObserver.bind(this) ;
      };

      if (this._content) {
        this._content.removeObserver('*',this._boundContentPropertyFunc) ;
      }
      this._content = value ;

      if (this._content) {
        this._content.addObserver('*',this._boundContentPropertyFunc) ;
      }
      
    }
    return this._content ;
  }.property(),
  contentBindingDefault: SC.Binding.Single,
  
  isEnabled: true,
  
  isValid: true,
  
  isCommitting: false,
  
  isDirty: false,
  
  canCommit: function(key,value) {
    return (this.isEnabled && !this.isCommitting) ;  
  }.property(),
  
  errors: null,
  
  errorCount: 0,
  
  autocommit: false,
  
  blockRegularSubmit: true,
  
  fields: {},
  
  validators: [],

  fieldNamePattern: /(.*)/,
  
  commitPrompt: null,

  outlets: ['errorExplanationView','formButtons','commitSpinner'],

  errorExplanationView: SC.View.outletFor('.error-explanation?'),
  formButtons: 'button',
  commitSpinner: 'img.spinner?',

  textFieldType: SC.TextFieldView,
  textareaFieldType: SC.TextFieldView,
  passwordFieldType: SC.TextFieldView,
  checkboxFieldType: SC.CheckboxFieldView,
  submitFieldType: SC.ButtonView,
  defaultFieldType: SC.FieldView,
  
  submitButtonType: SC.ButtonView.extend({
    action: function() { this.owner.commitForm(); }
  }),

  resetButtonType: SC.ButtonView.extend({
    action: function() { this.owner.resetForm(); }
  }),
    

  init: function() {
    arguments.callee.base.call(this) ;
    this.configureForm() ;
    
    var form = this;
    if (this.formButtons) { 
      this.formButtons = Array.asArray(this.formButtons).map(function(button){
        var opts = (button.type == 'submit') ? ({ isEnabledBinding: [form,'canCommit'] }) : ({}) ;
        var buttonType = form[(button.type || 'default') + 'ButtonType'] ;
        return (buttonType !== undefined) ? buttonType.viewFor(button,opts) : null ;
      }).compact() ;
    }

    if (this.commitSpinner) {
      this.commitSpinner = SC.SpinnerView.viewFor(this.commitSpinner,{
        isVisibleBinding: SC.Binding.Flag([this,'isCommitting'])
      }) ;
    } 
    
    this.registerDependentKey('canCommit','isValid','isEnabled',
      'isCommitting','isDirty') ;
    
  },

  configureForm: function() {
    var fields = {} ;
    
    this.beginPropertyChanges(); 
    
    var form = this.rootElement ;
    var validators = [] ;
    Form.getElements(form).each(function(field) {
      
      
      var fieldType = (field.tagName.toLowerCase() == 'textarea') ? 'textarea' : field.getAttribute('type');
      if (fieldType) fieldType = fieldType.toLowerCase() + 'FieldType' ;
      fieldType = form[fieldType] || form.defaultFieldType ;
      if (fieldType) {
        field = fieldType.viewFor(field) ;
        field.owner = form ;
      }
      
      var key = field.getAttribute('validate') ;
      field.validator = (key) ? SC.Validator.attachTo(form,field,key) : null ;

      var realTimeValidation = (field.getAttribute('realtimevalidation') == null || field.getAttribute('realtimevalidation') == "true") ? true : false;
      field.realTimeValidation = (realTimeValidation) ? true : false;

      var fieldKey = form.fieldKeyForField(field) ;
      if (!fields[fieldKey]) fields[fieldKey] = field ; 
      if (field.validator) validators.push(field.validator) ;
      
      Element.observe(field.rootElement,'change',function() { 
        form.fieldDidChange(field); 
      }) ;
    }) ;
    
    this.set('fields',fields); this.set('validators',validators) ;
    
    this.set('errors',null) ;

    var disabled = this.getAttribute("disabled") == "true" ;
    this.set('isEnabled',!disabled) ;
    
    this.endPropertyChanges() ;
  },
  
  validate: function() {
    
    if (!this.validators || this.validators.length==0) return true ;

    this.beginPropertyChanges(); 
    var ok = true ;
    this.validators.each(function(v) {
      var ret = v._validate() ;
      if (ret == false) ok = false ; 
    }) ;
    console.log('validate: ' + ok) ;
    this.set('isValid',ok) ;    
    this.endPropertyChanges() ;
    return ok ;
  },

  resetForm: function() {
    this.beginPropertyChanges() ;
    this.setCurrentFormValues(this.get('formValues')) ;
    this.set('errors',null) ; // clear errors
    this.set('errorCount',0) ;
    this.set('isDirty',false) ;
    this.endPropertyChanges() ;
    
    var content = this.get('content') ;
    if (content && content.cancel) content.cancel(this) ;
  },
  
  commitForm: function() {
    if (this.validate()) {
      var ret = true ;
      this._committing = true ;
      this.disable() ; 
      this.set('isCommitting',true) ;
      
      SC.Store.beginPropertyChanges(); // hold updating collections.
      
      try {
        var values = this.set('formValues',this.getCurrentFormValues()) ;
        
        var content = this.get('content') ;
        if (content) {
          content.beginPropertyChanges() ;
          for(var key in values) { content.set(key,values[key]) ; }
          content.endPropertyChanges() ;
          if (content.commit) content.commit(this) ; // Save changes
          this.set('isDirty',false) ;
        }
      }
      catch (e) {
        console.log("commit() exception: " + e) ;
        ret = false ;
      }
      
      SC.Store.endPropertyChanges(); // restore updating collections.
      
      this.set('isCommitting',false) ;
      this.enable() ;
      this._committing = false ;
      return ret ;
    } else return false ;
  },
  
  setErrorFor: function(field,message) {
    var errors = this.get('errors') || {} ;
    
    var key = (field) ? this.fieldKeyForField(field) : '__GENERAL__' ;
    errors[key] = message ;
    
    var errorCount = 0 ;
    for(var key in errors) {
      if (errors[key] == null) {
        delete errors[key] ;
      } else errorCount++ ;
    }
    
    if (errorCount == 0) errors = null ;
    
    this.beginPropertyChanges() ;
    this.set('errors',errors) ;
    this.set('errorCount',errorCount) ;
    this.endPropertyChanges() ;
  },
  
  getCurrentFormValues: function() {
    var ret = {} ;
    var fields = this.get('fields') || {} ;
    for(var key in fields) {
      var field = fields[key] ;
      ret[key] = (field && field.get) ? field.get('value') : null ;
    }
    return ret ;
  },

  setCurrentFormValues: function(values) {
    var fields = this.get('fields') || {} ;
    for (var key in fields) {
       var field = fields[key] ;
       var value = (values[key] == undefined || values[key] == null ) ? "" : values[key];
       if (field && field.set) field.set('value',value) ;      
    }  
  },  

  fieldDidChange: function(field) {
    if (!this.get('isDirty')) this.set('isDirty',true) ;
    if (this.get('autocommit')) this.commit() ;
  },

  fieldKeyForField: function(field) {
    var key = field.getAttribute('name') || field.getAttribute('id') ;
    var m = (key) ? key.match(this.fieldNamePattern) : null ;
    key = (m) ? m.pop() : key ;
    return (key) ? key.replace(/_/g,'-').camelize() : '' ;
  },

  
  _enabledObserver: function(target,key,isEnabled) {
    if (isEnabled == this._wasEnabled) return ;
    
    this._wasEnabled = isEnabled ;
    var fields = this.get('fields') ;
    for(var key in fields) {
      var field = fields[key] ;
      if (field.set) {
        field.set('isEnabled',isEnabled) ;
      } else {
        if (isEnabled) { 
          Form.Element.enable(field); 
        } else {
          Form.Element.disable(field) ;
        }
      }
    }
    
    if (this.formButtons) Array.asArray(this.formButtons).each(function(el) {
      if (el.set) {
        el.set('isEnabled',isEnabled) ;
      } else {
        if (isEnabled) { 
          Form.Element.enable(el); 
        } else {
          Form.Element.disable(el) ;
        }
      }
    }) ;
    
  }.observes('isEnabled'),
  
  _errorsObserver: function() {
    var messages = [] ; 
    var fields = this.get('fields') ;
    var errors = this.get('errors') || {} ;
    var fieldKeys = [] ;

    for(var key in fields) {
      var field = fields[key] ;
      if (field.set) field.set('isValid',!errors[key]) ;
      fieldKeys.push(key) ;
    } 

	var fieldSpecificMessages = [];
    for(var key in errors) { 
      var message = errors[key] ;
      if (fieldKeys.include(key)) { 
        fieldSpecificMessages.unshift(message); //see next code chunk down. Reversal.
      } else {
        messages.push(message) ;
      }
    }
	for(var i = fieldSpecificMessages.length-1; i>=0; i--)
		messages[messages.length]=(fieldSpecificMessages[i]); //make sure to add to end of messages.

    var html = '' ;
    if (messages.length > 0) {
      html = "<div>" + messages.join('</div><div>') + "</div>" ;
    }
    
    if (this.errorExplanationView) {
      this.errorExplanationView.set('asHTML',html) ;
      this.errorExplanationView.set('isVisible',(messages.length > 0)) ;
    }
    
    var isValid = this.get('errorCount') <= 0 ;
    if (isValid != this.get('isValid')) this.set('isValid',isValid) ;
    
  }.observes('errors'),
  
  _contentPropertyObserver: function(target,key,value) {
    if (key == '*') {
      var fields = this.get('fields') ;
      var changed = false ;
      var formValues = this.get('formValues') ;
      for(var key in fields) {
        var value = (target) ? target.get(key) : null ;
        if (value != formValues[key]) {
          formValues[key] = value ; changed = true ;
        }
      }
      if (changed) this.set('formValues',formValues) ;
    }
  },
  
  _contentObserver: function() {
    var content = this.get('content') ;
    
    var formValues = {} ;
    var fields = this.get('fields') ;
    for(var key in fields) {
      var value = (content) ? content.get(key) : null ;
      formValues[key] = value ;
    }
    
    this.beginPropertyChanges() ;
    this.set('isDirty',false) ;
    this.set('formValues',formValues) ;
    this.endPropertyChanges() ;
  }.observes('content'),
  
  onsubmit: function() { 
    this.commitForm() ;
    return !this.blockRegularSubmit; 
  }
}) ;


require('views/view') ;

lc_cnt = 0 ;

SC.ImageView = SC.View.extend({
  emptyElement: '<img />',
  
  status: 'unknown',
  
  content: null, // becomes the url.
  contentBindingDefault: SC.Binding.Single,
  
  transform: function(content) { return content; },
  
  contentObserver: function() {
    var prop = this.get('content') || '' ;
    var url = this.transform(prop) ;
    
    if (url && url.length > 0) {
      this.beginPropertyChanges() ;
      this.set('status','loading') ;
      SC.imageCache.loadImage(url, this, this._onLoadComplete) ;
      this.endPropertyChanges() ;
    } else {
      this.rootElement.src = '' ;
      this.set('status','unknown') ;
    }
  }.observes('content') ,
  
  _onLoadComplete: function(url, status, img) {  
    this.beginPropertyChanges() ;
    this.set('imageWidth', parseInt(img.width,0)) ;
    this.set('imageHeight', parseInt(img.height,0)) ;
    this.set('status',status) ;
    this.endPropertyChanges() ;
    
    if (status == 'loaded') {
      if (this.imageDidLoad) this.imageDidLoad(url) ;
      this.rootElement.src =  url ;
    } else {
      if (this.imageDidFail) this.imageDidFail(url, status) ;
    }
  },
  
  init: function() {
    arguments.callee.base.apply(this,arguments) ;
    if (this.rootElement.src) {
      this.set('imageWidth',parseInt(this.rootElement.width,0)) ;
      this.set('imageHeight',parseInt(this.rootElement.height,0)) ;
    }
  }
  
}) ;

SC.imageCache = SC.Object.create({  
  
  loadLimit: 4,
  
  loadImage: function(url, objOrFunc, method) {
    var dta = this._images[url] = (this._images[url] || { url: url,
      img: null, handlers: [], status: 'unknown' }) ;

    if (dta.img == null) {
      this._queue.push(dta) ;
      if (!this._imgTimeout) {
        this._imgTimeout = setTimeout(this.loadNextImage.bind(this),100) ;
      }
    }

    var handler = (method) ? [objOrFunc, method] : [this, objOrFunc] ;
    if (dta.status == 'unknown') {
      dta.handlers.push(handler) ;
    } else if (handler[1]) handler[1].call(handler[0], url, dta.status, dta.img) ;
  },
  
  loadNextImage: function() {
    this._imgTimeout = null ;
    while((this._queue.length > 0) && (this._loading.length < this.loadLimit)) {   
      var dta = this._queue.pop() ;
      var url = dta.url ;
      dta.img = new Image() ;
      dta.img.onabort = this._onAbort.bind(this,url) ;
      dta.img.onerror = this._onError.bind(this,url) ;
      dta.img.onload = this._onLoad.bind(this, url) ;
      dta.img.src = dta.url ;

      this._loading.push(dta.url) ;
    }
  },
  
  _onAbort: function(url) { this._changeStatus(url, 'aborted') ; },
  _onError: function(url) { this._changeStatus(url, 'error'); },
  _onLoad: function(url) { this._changeStatus(url, 'loaded'); },

  _changeStatus: function(url, status) {
    var dta = this._images[url] ;
    if (!dta) return ;
    dta.status = status ;
    
    var handler ;
    while(handler = dta.handlers.pop()) {
      if (handler[1]) handler[1].call(handler[0], url, dta.status, dta.img) ;
    }
    
    this._loading = this._loading.without(dta.url) ;
    this.loadNextImage() ;
  },
  
  _images: {},
  _loading: [],
  _queue: []
    
}); 

require('views/view') ;
require('controllers/collection') ;

SC.PaginationView = SC.View.extend({

  
  pageSize: 0,
  pageCount: 0,
  currentPage: 0,

  currentPageString: "_%@-%@ of %@",
  
  pageOptionString: "_Page %@: %@-%@",

  hasPreviousPage: function() {
    return this.get('currentPage') > 0 ;
  }.property(),
  
  hasNextPage: function() {
    return this.get('currentPage') < this.get('pageCount') ;
  }.property(),
   

  emptyElement: '<div class="pagination">\
    <button class="prev">«</button>\
    <button class="page"></button>\
    <button class="next">»</button>\
  </div>',

  outlets: ['prevButton','nextButton','pageButton'],
  
  prevButton: SC.ButtonView.extend({
    action: function() { this.owner.decrementProperty('currentPage'); },
    isEnabledBinding: "*owner.hasPreviousPage"
  })
  
}) ;



require('views/view') ;


SC.ProgressView = SC.View.extend({
  
  
  minimum: 0, // the minimum value of the progress 
  maximum: 1.0, // the maximum value of the progress bar.

  value: 0.50,
  valueBindingDefault: SC.Binding.SingleNotEmpty,
  
  isIndeterminate: function(key,value) {
    if (value !== undefined) {
      this._isIndeterminate = value ;
    }
    return this._isIndeterminate && (this.value != SC.Binding.EMPTY_PLACEHOLDER) ;
  }.property(),
  
  isEnabled: function(key, value) {
    if (value !== undefined) {
      this._isEnabled = value ;
    }
    return this._isEnabled && (this.value != SC.Binding.MULTIPLE_PLACEHOLDER) ;
  }.property(),
  
  _isIndeterminate: false, _isEnabled: true,
  
  
  emptyElement: '<div class="progress outer"><div class="outer-head"></div><div class="inner"><div class="inner-head"></div><div class="inner-tail"></div></div><div class="outer-tail"></div></div>',
  
  outlets: ['innerView'],
  innerView: SC.View.outletFor('.inner?'),
  
  
  propertyObserver: function(observing,target,key,value) {    
    if (['value','minimum','maximum','isIndeterminate','isEnabled'].include(key)) {
      var isIndeterminate = this.get('isIndeterminate') ;
      var isEnabled = this.get('isEnabled') ;
      
      Element.setClassName(this,'indeterminate',isIndeterminate) ;
      Element.setClassName(this,'disabled',!isEnabled) ;
      
      var value ;
      if (isIndeterminate || !isEnabled) {
        value = 0.0 ;
      } else {
        var minimum = this.get('minimum') || 0.0 ;
        var maximum = this.get('maximum') || 1.0 ;
        value = this.get('value') || 0.0 ;
        value = (value - minimum) / (maximum - minimum) ; 
        if (value > 1.0) value = 1.0 ;
      }
      value = value * 100 ;
      if (this.innerView) this.innerView.setStyle({ width: (value + '%') }) ;
    }
  }
}) ;









require('views/view') ;

SC.SegmentedView = SC.View.extend({
  
  selection: null, // set to the currently selection button.

  segments: null, // contains the array of buttons after init.
  
  isEnabled: true, 
  
  allowsEmptySelection: false,  

  init: function() {
    arguments.callee.base.call(this) ;
    
    if (!this.segments) this.segments = this.outlets.slice() ;
    var view = this ;
    this.segments.each(function(key) {
      var seg = view[key] ;
      var selectKey = key.slice(0,-6) ;
      if (seg && (seg.action == SC.ButtonView.prototype.action)) seg.action = function() {
        if (this.owner.get('allowsEmptySelection')) {
          newKey = (this.owner.get('selection') == selectKey) ? null : selectKey;
        } else newKey = selectKey;
        this.owner.set('selection',newKey) ;
      } ;
    }) ;
  },
  
  _selectionObserver: function() {
    var selection = this.get('selection') ;
    if (selection != this._lastSelected) {
      this._lastSelected = selection ;
      var view = this ;
      this.segments.each(function(key) {
        var isSelected = (selection) ? (key.slice(0,-6) == selection) : false;
        var button = view[key] ;
        if (button) button.set('isSelected',isSelected) ;
      }) ;
    }
  }.observes('selection'),
  
  _enabledObserver: function() {
    var isEnabled = this.get('isEnabled') ;
    if (isEnabled != this._lastEnabled) {
      var view = this ;
      this.segments.each(function(key) {
        view[key].set('isEnabled',isEnabled) ;
      }) ;
    }
  }.observes('isEnabled')
  
}) ;







require('views/field') ;

SC.SelectFieldView = SC.FieldView.extend({

 emptyElement: '<select></select>',

 objects: null,

 nameKey: null,

 sortKey: null,

 valueKey: null,

 emptyName: null,

 localize: true,

 sortObjects: function(objects) {
   var nameKey = this.get('sortKey') || this.get('nameKey') ;
   if (nameKey) {
     objects = objects.sort(function(a,b) {
       a = (typeof(a) == "string") ? a : a.get(nameKey) ;
       b = (typeof(b) == "string") ? b : b.get(nameKey) ;
       return (a<b) ? -1 : ((a>b) ? 1 : 0) ;
     }) ;
   }
   return objects ;
 },


 _rebuildMenu: function() {     
   var nameKey = this.get('nameKey') ;
   var valueKey = this.get('valueKey') ;
   var objects = this.get('objects') ;
   var fieldValue = this.get('value') ;

   if (!valueKey && fieldValue) fieldValue = fieldValue._guid ;
   if ((fieldValue == null) || (fieldValue == '')) fieldValue = '***' ;

   if (objects) {
     if (!(objects instanceof Array)) objects = [objects] ; // make array.
     objects = this.sortObjects(objects) ; // sort'em.
     var html = [] ;       
 
     var emptyName = this.get('emptyName') ;
     if (emptyName) {
       if (this.get('localize')) emptyName = emptyName.loc() ;
       html.push('<option value="***">' + emptyName + '</option>') ;
       html.push('<option disabled="disabled"></option>') ;
     }
 
     objects.each(function(object) {
       if (object) {
         var name = (nameKey) ? object.get(nameKey) : (''+object) ;
         var value = (valueKey) ? object.get(valueKey) : ((typeof(object) == "string") ? object : object._guid) ;
         html.push('<option value="'+value+'">'+name+'</option>') ;
     
       } else {
         html.push('<option disabled="disabled"></option>') ;
       }
     }) ;
 
     this.update(html.join(""));
     this.rootElement.value = fieldValue ;
 
   } else {
     this.set('value',null);
   }
 },

 didChange: function() {
   console.log('newValue = ' + this.rootElement.value) ;

   var valueKey = this.get('valueKey') ;
   var value = this.rootElement.value ;

   if (value == '***') {
     value = null ;
 
   } else if (value && (!valueKey)) {
     var objects = this.get('objects') ;
     if (objects instanceof Array) {
       var objectMatch = null ; var loc = objects.length ;
       while((--loc >= 0) && (!objectMatch)) {
         var object = objects[loc] ;
         var ov = (typeof(object) == "string") ? object : object._guid; 
         if (ov == value) objectMatch = object ;
       }
       value = objectMatch ;
   
     } else {
       var ov = (typeof(objects) == "string") ? objects : objects._guid; 
       value = (ov == value) ? objects : null ;
     }
   }

   if (this.rootElement.value != value) this.rootElement.value = value;
   this.propertyDidChange('value') ;
 },

 propertyObserver: function(observer,target,key,value) {
   if (target != this) return ; 

   if (key == 'value') {
     var valueKey = this.get('valueKey') ;
     if (!valueKey && value && value._guid) value = value._guid ;
     if ((value == null) || (value == '')) value = '***' ;
     this.rootElement.value = value ;
 
   } else if (!this._rebindTimeout) {
     this._rebindTimeout = setTimeout(this._rebindObjects.bind(this),1) ;
   }
 },

 _rebindObjects: function() {
   this._rebindTimeout = null ;
   if (!this._boundObserver) {
     this._boundObserver = this._rebuildMenu.bind(this) ;
   }
   var func = this._boundObserver ;

   if (this._objects) {
     if ((this._objects.length > 0) && (this._objects[0].removeObserver)) {
       this._objects.invoke('removeObserver',(this._nameKey || '*'), func) ;
       this._objects.invoke('removeObserver',(this._valueKey || '*'), func) ;
     }
   }

   var objects = this.get('objects') ;
   this._objects = (objects) ? ((objects instanceof Array) ? objects : [objects]) : null ;
   this._nameKey = this.get('nameKey') || '*' ;
   this._valueKey = this.get('valueKey') || '*' ;

   if (this._objects) {
     if ((this._objects.length > 0) && (this._objects[0].addObserver)) {
       this._objects.invoke('addObserver',this._nameKey,func) ;
       if (this._nameKey != this._valueKey) {
         this._objects.invoke('addObserver',this._valueKey,func) ;
       }
     }
   }

   this._rebuildMenu() ;
 },

 init: function() {
   arguments.callee.base.call(this) ;
   this._rebuildMenu() ;
 }
 
}) ;


require('views/view') ;

SC.SpinnerView = SC.View.extend({
  isVisibleBindingDefault: SC.Binding.Not
}) ;

require('views/view') ;
require('views/container') ;

SC.TabView = SC.ContainerView.extend({

  nowShowing: '',
  
  
  init: function() {
    arguments.callee.base.call(this) ;
    
    var tabs = {} ;
    var buttons = {} ;
    var view = this ;
    for(var outlet in this) {
      if (outlet.match(/Tab$/)) { // look for items ending in 'Tab'
        var key = outlet.slice(0,-3) ; // remove 'Tab' 
        var tab = view.get(outlet) ; // find tab view
        var button = view.get(key + 'Button') ; // find button view (opt)
        if (tab) {
          var tabId = tab.get('tabId') || key ;
          tabs[tabId] = tab ;
          if (button) buttons[tabId] = button ;          
        }
      }
    } ;
    
    this._tabs = tabs; this._buttons = buttons ;
    this.nowShowingObserver() ; // swap in/out the appropriate views.
  },
  
  nowShowingObserver: function() {
    var nowShowing = this.get('nowShowing') ;
    if (nowShowing == this._oldNowShowing) return ; // nothing to do.
    this._oldNowShowing = nowShowing ;
    for(var tabId in this._tabs) {
      var tab = this._tabs[tabId] ;
      var button = this._buttons[tabId] ;
      if (tabId == nowShowing) {
        if (button) button.set('isSelected',true) ;
      } else { 
        tab.set('isVisible',false) ;
        if (button) button.set('isSelected',false) ;
      }      
    }
    
    var visibleTab = this._tabs[nowShowing] ;
    this.set('content',visibleTab) ;
    if (visibleTab) { visibleTab.set('isVisible',true); }
    
  }.observes('nowShowing')
  
}) ;



SC.Toolbar = SC.View.extend({
  
  buttons: [],
  
  isEnabled: true,
  
  init: function() {
    arguments.callee.base.call(this) ;
    var toolbar = this ;
    this.buttons = this.buttons.map(function(button) {
      return button(toolbar) ; // get outlet.
    }) ;
  },
  
  isEnabledObserver: function() {
    var e = this.get('isEnabled') ;
    this.get('buttons').each(function(button) { button.set('isEnabled',e); }) ;
  }.observes('isEnabled')
  
}) ;

require('views/tab') ;
require('foundation/routes') ;
require('views/button') ;

SC.WorkspaceView = SC.TabView.extend({
  
  areas: ['head','master','detail','dialog','tail'],
  
  workspaces: [],
  namespace: null,
  
  elements: function() {
    var view = this ;
    var ret = [] ;
    this.areas.each(function(area) {
      var el = view.$C(area) ;
      if (el) { 
        view[area + 'Element'] = SC.ContainerView.viewFor(el); 
        ret.push(el) ;
      }
    }) ;
    return ret ;    
  },
  
  replaceContent: function(newContent) {
    var view = this ;
    this.areas.each(function(area) {
      var key = area + 'Element' ;
      var newElement = newContent.get(key) ;      
      var viewArea = view[key] ;
      
      if (viewArea) {
        viewArea.set('content',newElement) ;
      } else {
        console.log('WARNING: could not find view area: ' + key) ;
      }
    }) ;
    
    if (this._nowShowingContent) {
      this._nowShowingContent.set('nowShowing',null) ;
    }
    newContent.set('nowShowing',this.get('nowShowing')) ;
    this._nowShowingContent = newContent ;
  },
  
  init: function() {
    if (this.namespace && this.workspaces && this.workspaces.length > 0) {
      var view = this ; var ns = this.namespace ;
      this.workspaces.each(function(key) {
        key = key.toLowerCase() ;
        var tab = window[ns][key.capitalize() + 'Workspace'] ;
        if (tab && !(view[key + 'Tab'])) {
          tab = tab.viewFor(key + '-workspace') ;
          view[key + 'Tab'] = tab ;
        }
        
        var button = $(key) ;
        if (button) view[key + 'Button'] = SC._WorkspaceButton.viewFor(key,{
          workspace: view, workspaceId: key
        }) ;
      }) ;
    }
    
    arguments.callee.base.call(this) ;
    
    this.elements = this.elements() ;
    
    SC.Routes.addRoute(':workspace',this._showWorkspace.bind(this)) ; 
  },
  
  _showWorkspace: function(params) { 
    if (this._tabs[params.workspace]) {
      this.beginPropertyChanges() ;
      this.set('nowShowing',params.workspace) ;
      this.set('options',params) ;
      this.endPropertyChanges() ;
    } else console.log('workspace not found: ' + params.workspace) ;
  }
  
}) ;

SC._WorkspaceButton = SC.ButtonView.extend({
  workspace: null, workspaceId: null,
  action: function() { SC.Routes.set('location',this.workspaceId); },
  isSelectedObserver: function() {
    var parent = this.parentNode ;
    if (parent) Element.setClassName($(parent),'sel',this.get('isSelected'));
  }.observes('isSelected') 
}),

SC.WorkspaceTab = SC.View.extend({

  
  areas: ['head','master','detail','dialog','tail'],

  viewType: SC.View,

  title: null,
  
  location: null,
  
  filterString: null,

  nowShowing: null,

  elements: function() {
    var view = this ;
    var ret = [] ;
    this.areas.each(function(area) {
      var el = view.$C(area,1) ;
      if (el) {
        if (this.viewType) el = this.viewType.viewFor(el) ;
        view[area + 'Element'] = el ; ret.push(el) ;
      }
    }) ;
    return ret ;
  },
  
  init: function() {
    arguments.callee.base.apply(this,arguments) ;
    this.elements = this.elements() ;
  }
  
}) ;

